Skip to content

Commit

Permalink
test: add mainnet reference test for BLS
Browse files Browse the repository at this point in the history
  • Loading branch information
Jeday committed Feb 28, 2025
1 parent 15350e5 commit 265dad4
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 91 deletions.
60 changes: 0 additions & 60 deletions test/0.8.25/vaults/BLS.test.ts

This file was deleted.

77 changes: 77 additions & 0 deletions test/0.8.25/vaults/BLS/BLS.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { expect } from "chai";

import { SecretKey, verify } from "@chainsafe/blst";

import { ether } from "lib";

import { computeDepositMessageRoot, extractYCoordinates, verifyDepositMessage } from "./bls-utils";

// test deposit data
const STATIC_DEPOSIT = {
amount: ether("1"),
testPrivateKey: "0x18f020b98eb798752a50ed0563b079c125b0db5dd0b1060d1c1b47d4a193e1e4",
withdrawalCredentials: "0xf3d93f9fbc6a229f3b11340b4b52ae53833813efab76e812d1d014163259ef1f",
};

// actual deposit from mainnet validator
const MAINNET_DEPOSIT_MESSAGE = {
pubkey: "0x88841E426F271030AD2257537F4EABD216B891DA850C1E0E2B92EE0D6E2052B1DAC5F2D87BEF51B8AC19D425ED024DD1",
withdrawalCredentials: "0x004AAD923FC63B40BE3DDE294BDD1BBB064E34A4A4D51B68843FEA44532D6147",
amount: ether("32"),
signature:
"0x99A9E9ABD7D4A4DE2D33B9C3253FF8440AD237378CE37250D96D5833FE84BA87BBF288BF3825763C04C3B8CDBA323A3B02D542CDF5940881F55E5773766B1B185D9CA7B6E239BDD3FB748F36C0F96F6A00D2E1D314760011F2F17988E248541D",
};

describe("BLS.sol", () => {
it("can create a deposit from test key", async () => {
// deposit message
const privateKey = SecretKey.fromHex(STATIC_DEPOSIT.testPrivateKey);
const pubkey = privateKey.toPublicKey();
const pubkeyCompressed = pubkey.toHex(true);

const withdrawalCredentials = STATIC_DEPOSIT.withdrawalCredentials;
const amount = STATIC_DEPOSIT.amount;

// deposit message + domain
const message = await computeDepositMessageRoot(pubkey.toHex(true), withdrawalCredentials, amount);
const messageHex = Buffer.from(message).toString("hex");

const signature = privateKey.sign(message);
const signatureCompressed = signature.toHex(true);

const result = verify(message, pubkey, signature);
expect(result).to.be.true;

// Mock data for forge test
const depositTestObject = {
withdrawalCredentials: withdrawalCredentials,
messageHex,
...extractYCoordinates(pubkeyCompressed, signatureCompressed),
};

//console.log(depositTestObject);

expect(depositTestObject).to.not.be.undefined;
});

it("can create full signature from existing validator deposit message", async () => {
expect(
await verifyDepositMessage(
MAINNET_DEPOSIT_MESSAGE.pubkey,
MAINNET_DEPOSIT_MESSAGE.withdrawalCredentials,
MAINNET_DEPOSIT_MESSAGE.amount,
MAINNET_DEPOSIT_MESSAGE.signature,
),
).to.be.true;

// Mainnet data for forge test
const depositTestObject = {
withdrawalCredentials: MAINNET_DEPOSIT_MESSAGE.withdrawalCredentials,
...extractYCoordinates(MAINNET_DEPOSIT_MESSAGE.pubkey, MAINNET_DEPOSIT_MESSAGE.signature),
};

//console.log(depositTestObject);

expect(depositTestObject).to.not.be.undefined;
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { PublicKey, Signature, verify } from "@chainsafe/blst";

type ByteArray = Uint8Array;

export const sanitazeHex = (hex: string) => hex.replace("0x", "").toLowerCase();

export const toHexString = (value: unknown): string => {
if (typeof value === "string" && !value.startsWith("0x")) {
return `0x${value}`;
Expand Down Expand Up @@ -99,3 +103,42 @@ export const computeDepositMessageRoot = async (
domain,
});
};

export const extractYCoordinates = (pubkey: string, signature: string) => {
const pubkeyObj = PublicKey.fromHex(sanitazeHex(pubkey));
const signatureObj = Signature.fromHex(sanitazeHex(signature));

// Y coordinate of Fp component of pubkey is last 48 bytes of uncompressed pubkey(g1 point)
const pubkeyY = Buffer.from(pubkeyObj.toBytes(false).slice(48)).toString("hex");
// the signature is a G2 point, so we need to extract the two components of Y coordinate (which is Fp2) from it
// first Fp of Y coordinate is last 48 bytes of signature
const sigY_c0 = Buffer.from(signatureObj.toBytes(false).slice(96 + 48, 96 + 48 * 2)).toString("hex");
// second Fp is 48 bytes before first one
const sigY_c1 = Buffer.from(signatureObj.toBytes(false).slice(96, 96 + 48)).toString("hex");

return {
pubkey: pubkeyObj.toHex(true),
pubkeyY,
pubkeyFull: pubkeyObj.toHex(false),
signature: signatureObj.toHex(true),
signatureY: {
c0: sigY_c0,
c1: sigY_c1,
},
signatureFull: signatureObj.toHex(false),
};
};

export const verifyDepositMessage = async (
pubkey: string,
withdrawalCredentials: string,
amount: bigint,
signature: string,
) => {
const message = await computeDepositMessageRoot(pubkey, withdrawalCredentials, amount);

const pubkeyObj = PublicKey.fromHex(sanitazeHex(pubkey));
const signatureObj = Signature.fromHex(sanitazeHex(signature));

return verify(message, pubkeyObj, signatureObj);
};
72 changes: 41 additions & 31 deletions test/0.8.25/vaults/contracts/BLS.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ import {StdAssertions} from "forge-std/StdAssertions.sol";

import {BLS, SSZ} from "contracts/0.8.25/lib/BLS.sol";

struct PrecomputedDepositMessage {
bytes pubkey;
bytes32 withdrawal;
uint256 amount;
bytes signature;
BLS.Fp pubkeyYComponent;
BLS.Fp2 signatureYComponent;
bytes32 validMsgHash;
}

// harness to test methods with calldata args
contract BLSHarness is StdUtils {
function verifyDepositMessage(
bytes calldata pubkey,
Expand All @@ -35,37 +46,6 @@ contract BLSHarness is StdUtils {
) public view returns (bytes32) {
return SSZ.depositMessageSigningRoot(pubkey, withdrawal, amount);
}

function hashToFieldFp2(
bytes calldata pubkey,
bytes32 withdrawal,
uint64 amount
) public view returns (BLS.Fp2[2] memory) {
bytes32 _msg = SSZ.depositMessageSigningRoot(pubkey, withdrawal, amount);
return BLS.hashToFieldFp2(_msg, BLS.DST);
}

function hashToCurveG2(
bytes calldata pubkey,
bytes32 withdrawal,
uint64 amount
) public view returns (BLS.G2Point memory) {
return BLS.hashToCurveG2(SSZ.depositMessageSigningRoot(pubkey, withdrawal, amount));
}

function NEGATED_G1_GENERATOR() public pure returns (BLS.G1Point memory) {
return BLS.NEGATED_G1_GENERATOR();
}
}

struct PrecomputedDepositMessage {
bytes pubkey;
bytes32 withdrawal;
uint256 amount;
bytes signature;
BLS.Fp pubkeyYComponent;
BLS.Fp2 signatureYComponent;
bytes32 validMsgHash;
}

contract BLSVerifyingKeyTest is Test {
Expand All @@ -81,6 +61,18 @@ contract BLSVerifyingKeyTest is Test {
StdAssertions.assertEq(root, deposit.validMsgHash);
}

function test_verifyMainnetDeposit() external view {
PrecomputedDepositMessage memory deposit = STATIC_MAINNET_MESSAGE();
harness.verifyDepositMessage(
deposit.pubkey,
deposit.withdrawal,
deposit.amount,
deposit.signature,
deposit.pubkeyYComponent,
deposit.signatureYComponent
);
}

function test_verifyDeposit() external view {
PrecomputedDepositMessage memory deposit = STATIC_DEPOSIT_MESSAGE();
harness.verifyDepositMessage(
Expand Down Expand Up @@ -142,6 +134,24 @@ contract BLSVerifyingKeyTest is Test {
);
}

function STATIC_MAINNET_MESSAGE() internal pure returns (PrecomputedDepositMessage memory) {
return
PrecomputedDepositMessage(
hex"88841e426f271030ad2257537f4eabd216b891da850c1e0e2b92ee0d6e2052b1dac5f2d87bef51b8ac19d425ed024dd1",
0x004AAD923FC63B40BE3DDE294BDD1BBB064E34A4A4D51B68843FEA44532D6147,
1 ether,
hex"99a9e9abd7d4a4de2d33b9c3253ff8440ad237378ce37250d96d5833fe84ba87bbf288bf3825763c04c3b8cdba323a3b02d542cdf5940881f55e5773766b1b185d9ca7b6e239bdd3fb748f36c0f96f6a00d2e1d314760011f2f17988e248541d",
wrapFp(
hex"04c46736f0aa8ec7e6e4c1126c12079f09dc28657695f13154565c9c31907422f48df41577401bab284458bf4ebfb45d"
),
wrapFp2(
hex"10e7847980f47ceb3f994a97e246aa1d563dfb50c372156b0eaee0802811cd62da8325ebd37a1a498ad4728b5852872f",
hex"00c4aac6c84c230a670b4d4c53f74c0b2ca4a6a86fe720d0640d725d19d289ce4ac3a9f8a9c8aa345e36577c117e7dd6"
),
0xa0ea5aa96388d0375c9181eac29fa198cea873c818efe7442bd49c03948f2a69
);
}

function wrapFp(bytes memory data) internal pure returns (BLS.Fp memory) {
require(data.length == 48, "Invalid Fp length");
uint256 a = BLS.sliceToUint(data, 0, 16);
Expand Down

0 comments on commit 265dad4

Please sign in to comment.