Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions packages/state-transition/src/util/blobs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import SHA256 from "@chainsafe/as-sha256";
import {byteArrayEquals} from "@chainsafe/ssz";
import {BLOB_TX_TYPE, VERSIONED_HASH_VERSION_KZG} from "@lodestar/params";
import {bellatrix, eip4844} from "@lodestar/types";
import {toHex} from "@lodestar/utils";

// TODO EIP-4844: Move to params
const BYTES_PER_HASH = 32;

/**
* Blob transaction:
* - 1 byte prefix
* - class SignedBlobTransaction(Container): message starts at offset 69
* - class BlobTransaction(Container): blob_versioned_hashes offset value in offset 188, last property in container
* So to read blob_versioned_hashes:
* - Read offset value at [70+188, 70+188+4]
* - Read chunks between offset value and EOF
* Reference: https://gist.github.com/protolambda/23bd106b66f6d4bb854ce46044aa3ca3
*/
export const OPAQUE_TX_MESSAGE_OFFSET = 70;
export const OPAQUE_TX_BLOB_VERSIONED_HASHES_OFFSET = OPAQUE_TX_MESSAGE_OFFSET + 188;

type VersionHash = Uint8Array;

/**
* https://github.com/ethereum/consensus-specs/blob/11a037fd9227e29ee809c9397b09f8cc3383a8c0/specs/eip4844/beacon-chain.md#verify_kzg_commitments_against_transactions
* No expensive verification, just checks that the version hashes are consistent with the kzg commitments
*/
export function verifyKzgCommitmentsAgainstTransactions(
transactions: bellatrix.Transaction[],
blobKzgCommitments: eip4844.KZGCommitment[]
): boolean {
const allVersionedHashes: VersionHash[] = [];
for (const tx of transactions) {
if (tx[0] === BLOB_TX_TYPE) {
// TODO EIP-4844: Optimize array manipulation
allVersionedHashes.push(...txPeekBlobVersionedHashes(tx));
}
}

if (allVersionedHashes.length !== blobKzgCommitments.length) {
throw Error(
`allVersionedHashes len ${allVersionedHashes.length} != blobKzgCommitments len ${blobKzgCommitments.length}`
);
}

for (let i = 0; i < blobKzgCommitments.length; i++) {
const versionedHash = kzgCommitmentToVersionedHash(blobKzgCommitments[i]);
if (!byteArrayEquals(allVersionedHashes[i], versionedHash)) {
throw Error(`Wrong versionedHash ${i} ${toHex(allVersionedHashes[i])} != ${toHex(versionedHash)}`);
}
}

// TODO EIP-4844: Use proper API, either throw error or return boolean
return true;
}

function txPeekBlobVersionedHashes(opaqueTx: bellatrix.Transaction): VersionHash[] {
if (opaqueTx[0] !== BLOB_TX_TYPE) {
throw Error(`tx type ${opaqueTx[0]} != BLOB_TX_TYPE`);
}

const opaqueTxDv = new DataView(opaqueTx.buffer, opaqueTx.byteOffset, opaqueTx.byteLength);

const blobVersionedHashesOffset =
OPAQUE_TX_MESSAGE_OFFSET + opaqueTxDv.getUint32(OPAQUE_TX_BLOB_VERSIONED_HASHES_OFFSET, true);

// Guard against offsets that go beyond end of bytes
if (blobVersionedHashesOffset > opaqueTx.length) {
throw Error(`blobVersionedHashesOffset ${blobVersionedHashesOffset} > EOF ${opaqueTx.length}`);
}

// Guard against not multiple of BYTES_PER_HASH
const blobVersionedHashesByteLen = opaqueTx.length - blobVersionedHashesOffset;
if ((opaqueTx.length - blobVersionedHashesOffset) % BYTES_PER_HASH !== 0) {
throw Error(`Uneven blobVersionedHashesByteLen ${blobVersionedHashesByteLen}`);
}

const versionedHashes: VersionHash[] = [];

// iterate from x to end of data, in steps of 32, to get all hashes
for (let i = blobVersionedHashesOffset; i < opaqueTx.length; i += BYTES_PER_HASH) {
versionedHashes.push(opaqueTx.subarray(i, i + BYTES_PER_HASH));
}

return versionedHashes;
}

export function kzgCommitmentToVersionedHash(kzgCommitment: eip4844.KZGCommitment): VersionHash {
const hash = SHA256.digest(kzgCommitment);
// Equivalent to `VERSIONED_HASH_VERSION_KZG + hash(kzg_commitment)[1:]`
hash[0] = VERSIONED_HASH_VERSION_KZG;
return hash;
}
1 change: 1 addition & 0 deletions packages/state-transition/src/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from "./array.js";
export * from "./attestation.js";
export * from "./attesterStatus.js";
export * from "./balance.js";
export * from "./blobs.js";
export * from "./capella.js";
export * from "./execution.js";
export * from "./blockRoot.js";
Expand Down
53 changes: 53 additions & 0 deletions packages/state-transition/test/unit/util/blobs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {fromHex} from "@lodestar/utils";
import {verifyKzgCommitmentsAgainstTransactions} from "../../../src/index.js";

// Data from fee-merket test at Inphi/eip4844-interop
// Ref https://github.com/Inphi/eip4844-interop/blob/cad0dab50901cc1371a683388136fd56654d3bba/tests/fee-market/main.go#L67
const tx =
"05450000000058aca83353114c8385ece4c18461dbb8d1ef7479c3640e812acd2ec488c56fcf43e305a508bc30bec9bff27ab367a1ed0b397b0299bb7f48b72884f5178e7c0a0100000000000000000000000000000000000000000000000000000000000000000000000000000000f2052a0100000000000000000000000000000000000000000000000000000000f2052a010000000000000000000000000000000000000000000000000000005034030000000000c00000004e61bc0000000000000000000000000000000000000000000000000000000000d5000000d5000000005ed0b200000000000000000000000000000000000000000000000000000000d500000001ffb38a7a99e3e2335be83fc74b7faa19d553124301134066927e042d676d93e523ef251e0b82bdcb72d2ca85e99c804f60ffa989";
const blobKzgCommitment =
"a1a6202ee4a387c16ba23379b9c471a0cc48cb53793678faa92b2920c270867eebeac74351946859ffd199c6fe32385e";

describe("blobs", () => {
it("verifyKzgCommitmentsAgainstTransactions", () => {
verifyKzgCommitmentsAgainstTransactions([fromHex(tx)], [fromHex(blobKzgCommitment)]);
});

// 05
// 45000000
// 0058aca83353114c8385ece4c18461dbb8d1ef7479c3640e812acd2ec488c56fcf43e305a508bc30bec9bff27ab367a1ed0b397b0299bb7f48b72884f5178e7c0a
// 000 - 0100000000000000000000000000000000000000000000000000000000000000 // chain_id
// 032 - 0000000000000000 // nonce
// 040 - 00f2052a01000000000000000000000000000000000000000000000000000000 // max_priority_fee_per_gas
// 072 - 00f2052a01000000000000000000000000000000000000000000000000000000 // max_fee_per_gas
// 104 - 5034030000000000 // gas
// 112 - c0000000 // to - offset_value = 192
// 116 - 4e61bc0000000000000000000000000000000000000000000000000000000000 // value
// 148 - d5000000 // data
// 152 - d5000000 // access_list
// 156 - 005ed0b200000000000000000000000000000000000000000000000000000000 // max_fee_per_data_gas
// 188 - d5000000 // blob_versioned_hashes - offset_value = 213
// 192 - 01 ffb38a7a99e3e2335be83fc74b7faa19d5531243 // __value_of to
// 213 - 01134066927e042d676d93e523ef251e0b82bdcb72d2ca85e99c804f60ffa989 // __value_of blob_versioned_hashes
//
// field offset: 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 + 32 = 188

// class SignedBlobTransaction(Container):
// message: BlobTransaction // 4 bytes offset, continues on 69
// signature: ECDSASignature // 65 bytes

// class BlobTransaction(Container):
// chain_id: uint256 // 32 bytes
// nonce: uint64 // 8 bytes
// max_priority_fee_per_gas: uint256 // 32 bytes
// max_fee_per_gas: uint256 // 32 bytes
// gas: uint64 // 8 bytes
// to: Union[None, Address] # Address = Bytes20 // 4 bytes offset
// value: uint256 // 32 bytes
// data: ByteList[MAX_CALLDATA_SIZE] // 4 bytes offset
// access_list: List[AccessTuple, MAX_ACCESS_LIST_SIZE] // 4 bytes offset
// max_fee_per_data_gas: uint256 # new in PR 5707, a.k.a. fee market change of EIP-4844 // 32 bytes offset
// blob_versioned_hashes: List[VersionedHash, MAX_VERSIONED_HASHES_LIST_SIZE] // 4 bytes offset

// field_offset = 32 + 8 + 32 + 32 + 8 + 4 + 32 + 4 + 4 + 32 = 188
});