diff --git a/packages/tx/src/4844/constructors.ts b/packages/tx/src/4844/constructors.ts index b832ee5cf91..24f4b446f65 100644 --- a/packages/tx/src/4844/constructors.ts +++ b/packages/tx/src/4844/constructors.ts @@ -3,6 +3,7 @@ import { CELLS_PER_EXT_BLOB, EthereumJSErrorWithoutCode, bigIntToHex, + blobsToCellProofs, blobsToCells, blobsToCommitments, blobsToProofs, @@ -86,16 +87,40 @@ const validateBlobTransactionNetworkWrapper = ( } /** - * Instantiate a transaction from a data dictionary. + * Instantiate a Blob4844Tx transaction from a data dictionary. * - * Format: { chainId, nonce, gasPrice, gasLimit, to, value, data, accessList, - * v, r, s, blobs, kzgCommitments, blobVersionedHashes, kzgProofs } + * If blobs are provided the tx will be instantiated in the "Network Wrapper" format, + * otherwise in the canonical form represented on-chain. + * + * @param txData - Transaction data object containing: + * - `chainId` - Chain ID (will be set automatically if not provided) + * - `nonce` - Transaction nonce + * - `maxPriorityFeePerGas` - Maximum priority fee per gas (EIP-1559) + * - `maxFeePerGas` - Maximum fee per gas (EIP-1559) + * - `gasLimit` - Gas limit for the transaction + * - `to` - Recipient address (optional for contract creation) + * - `value` - Value to transfer in wei + * - `data` - Transaction data + * - `accessList` - Access list for EIP-2930 (optional) + * - `maxFeePerBlobGas` - Maximum fee per blob gas (EIP-4844) + * - `blobVersionedHashes` - Versioned hashes for blob validation + * - `v`, `r`, `s` - Signature components (for signed transactions) + * - `blobs` - Raw blob data (optional, will derive commitments/proofs) + * - `blobsData` - Array of strings to construct blobs from (optional) + * - `kzgCommitments` - KZG commitments (optional, derived from blobs if not provided) + * - `kzgProofs` - KZG proofs (optional, derived from blobs if not provided) + * - `networkWrapperVersion` - Network wrapper version (0=EIP-4844, 1=EIP-7594) + * @param opts - Transaction options including Common instance with KZG initialized + * @returns A new Blob4844Tx instance + * + * @throws {EthereumJSErrorWithoutCode} If KZG is not initialized in Common + * @throws {EthereumJSErrorWithoutCode} If both blobsData and blobs are provided * * Notes: - * - `chainId` will be set automatically if not provided - * - All parameters are optional and have some basic default values - * - `blobs` cannot be supplied as well as `kzgCommitments`, `blobVersionedHashes`, `kzgProofs` - * - If `blobs` is passed in, `kzgCommitments`, `blobVersionedHashes`, `kzgProofs` will be derived by the constructor + * - Requires a Common instance with `customCrypto.kzg` initialized + * - Cannot provide both `blobsData` and `blobs` simultaneously + * - If `blobs` or `blobsData` is provided, `kzgCommitments`, `blobVersionedHashes`, and `kzgProofs` will be automatically derived + * - KZG proof type depends on EIP-7594 activation: per-Blob proofs (EIP-4844) or per-Cell proofs (EIP-7594) */ export function createBlob4844Tx(txData: TxData, opts?: TxOptions) { if (opts?.common?.customCrypto?.kzg === undefined) { @@ -110,23 +135,6 @@ export function createBlob4844Tx(txData: TxData, opts?: TxOptions) { 'cannot have both raw blobs data and encoded blobs in constructor', ) } - if (txData.blobsData !== undefined) { - if (txData.kzgCommitments !== undefined) { - throw EthereumJSErrorWithoutCode( - 'cannot have both raw blobs data and KZG commitments in constructor', - ) - } - if (txData.blobVersionedHashes !== undefined) { - throw EthereumJSErrorWithoutCode( - 'cannot have both raw blobs data and versioned hashes in constructor', - ) - } - if (txData.kzgProofs !== undefined) { - throw EthereumJSErrorWithoutCode( - 'cannot have both raw blobs data and KZG proofs in constructor', - ) - } - } if (txData.blobsData !== undefined || txData.blobs !== undefined) { txData.blobs ??= getBlobs( txData.blobsData!.reduce((acc, cur) => acc + cur), @@ -135,21 +143,52 @@ export function createBlob4844Tx(txData: TxData, opts?: TxOptions) { txData.blobVersionedHashes ??= commitmentsToVersionedHashes( txData.kzgCommitments as PrefixedHexString[], ) - txData.kzgProofs ??= blobsToProofs( - kzg, - txData.blobs as PrefixedHexString[], - txData.kzgCommitments as PrefixedHexString[], - ) + if (opts!.common!.isActivatedEIP(7594)) { + txData.kzgProofs ??= blobsToCellProofs(kzg, txData.blobs as PrefixedHexString[]) + } else { + txData.kzgProofs ??= blobsToProofs( + kzg, + txData.blobs as PrefixedHexString[], + txData.kzgCommitments as PrefixedHexString[], + ) + } } return new Blob4844Tx(txData, opts) } /** - * Create a transaction from an array of byte encoded values ordered according to the devp2p network encoding - format noted below. + * Create a Blob4844Tx transaction from an array of byte encoded values ordered according to the devp2p network encoding. + * Only canonical format supported, otherwise use `createBlob4844TxFromSerializedNetworkWrapper()`. + * + * @param values - Array of byte encoded values containing: + * - `chainId` - Chain ID as Uint8Array + * - `nonce` - Transaction nonce as Uint8Array + * - `maxPriorityFeePerGas` - Maximum priority fee per gas (EIP-1559) as Uint8Array + * - `maxFeePerGas` - Maximum fee per gas (EIP-1559) as Uint8Array + * - `gasLimit` - Gas limit for the transaction as Uint8Array + * - `to` - Recipient address as Uint8Array (optional for contract creation) + * - `value` - Value to transfer in wei as Uint8Array + * - `data` - Transaction data as Uint8Array + * - `accessList` - Access list for EIP-2930 as Uint8Array (optional) + * - `maxFeePerBlobGas` - Maximum fee per blob gas (EIP-4844) as Uint8Array + * - `blobVersionedHashes` - Versioned hashes for blob validation as Uint8Array[] + * - `v` - Signature recovery ID as Uint8Array (for signed transactions) + * - `r` - Signature r component as Uint8Array (for signed transactions) + * - `s` - Signature s component as Uint8Array (for signed transactions) + * @param opts - Transaction options including Common instance with KZG initialized + * @returns A new Blob4844Tx instance + * + * @throws {EthereumJSErrorWithoutCode} If KZG is not initialized in Common + * @throws {EthereumJSErrorWithoutCode} If values array length is not 11 (unsigned) or 14 (signed) * * Format: `[chainId, nonce, maxPriorityFeePerGas, maxFeePerGas, gasLimit, to, value, data, - * accessList, signatureYParity, signatureR, signatureS]` + * accessList, maxFeePerBlobGas, blobVersionedHashes, v, r, s]` + * + * Notes: + * - Requires a Common instance with `customCrypto.kzg` initialized + * - Supports both unsigned (11 values) and signed (14 values) transaction formats + * - All numeric values must be provided as Uint8Array byte representations */ export function createBlob4844TxFromBytesArray(values: TxValuesArray, opts: TxOptions = {}) { if (opts.common?.customCrypto?.kzg === undefined) { @@ -216,10 +255,25 @@ export function createBlob4844TxFromBytesArray(values: TxValuesArray, opts: TxOp } /** - * Instantiate a transaction from a RLP serialized tx. + * Instantiate a Blob4844Tx transaction from an RLP serialized transaction. + * Only canonical format supported, otherwise use `createBlob4844TxFromSerializedNetworkWrapper()`. + * + * @param serialized - RLP serialized transaction data as Uint8Array + * @param opts - Transaction options including Common instance with KZG initialized + * @returns A new Blob4844Tx instance + * + * @throws {EthereumJSErrorWithoutCode} If KZG is not initialized in Common + * @throws {EthereumJSErrorWithoutCode} If serialized data is not a valid EIP-4844 transaction + * @throws {EthereumJSErrorWithoutCode} If RLP decoded data is not an array * * Format: `0x03 || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, - * access_list, max_fee_per_data_gas, blob_versioned_hashes, y_parity, r, s])` + * access_list, max_fee_per_blob_gas, blob_versioned_hashes, y_parity, r, s])` + * + * Notes: + * - Requires a Common instance with `customCrypto.kzg` initialized + * - Transaction type byte must be 0x03 (BlobEIP4844) + * - RLP payload must decode to an array of transaction fields + * - Delegates to `createBlob4844TxFromBytesArray` for actual construction */ export function createBlob4844TxFromRLP(serialized: Uint8Array, opts: TxOptions = {}) { if (opts.common?.customCrypto?.kzg === undefined) { @@ -246,10 +300,32 @@ export function createBlob4844TxFromRLP(serialized: Uint8Array, opts: TxOptions } /** - * Creates a transaction from the network encoding of a blob transaction (with blobs/commitments/proof) - * @param serialized a buffer representing a serialized BlobTransactionNetworkWrapper - * @param opts any TxOptions defined - * @returns a Blob4844Tx + * Creates a Blob4844Tx transaction from the network encoding of a blob transaction wrapper. + * This function handles the "Network Wrapper" format that includes blobs, commitments, and proofs. + * + * @param serialized - Serialized BlobTransactionNetworkWrapper as Uint8Array + * @param opts - Transaction options including Common instance with KZG initialized + * @returns A new Blob4844Tx instance with network wrapper data + * + * @throws {EthereumJSErrorWithoutCode} If Common instance is not provided + * @throws {EthereumJSErrorWithoutCode} If KZG is not initialized in Common + * @throws {EthereumJSErrorWithoutCode} If serialized data is not a valid EIP-4844 transaction + * @throws {Error} If network wrapper has invalid number of values (not 4 or 5) + * @throws {Error} If transaction has no valid `to` address + * @throws {Error} If network wrapper version is invalid + * @throws {EthereumJSErrorWithoutCode} If KZG verification fails + * @throws {EthereumJSErrorWithoutCode} If versioned hashes don't match commitments + * + * Network Wrapper Formats: + * - EIP-4844: `0x03 || rlp([tx_values, blobs, kzg_commitments, kzg_proofs])` (4 values) + * - EIP-7594: `0x03 || rlp([tx_values, network_wrapper_version, blobs, kzg_commitments, kzg_proofs])` (5 values) + * + * Notes: + * - Requires a Common instance with `customCrypto.kzg` initialized + * - Validates KZG proofs against blobs and commitments + * - Verifies versioned hashes match computed commitments + * - Supports both EIP-4844 and EIP-7594 network wrapper formats + * - Transaction is frozen by default unless `opts.freeze` is set to false */ export function createBlob4844TxFromSerializedNetworkWrapper( serialized: Uint8Array, diff --git a/packages/tx/src/4844/tx.ts b/packages/tx/src/4844/tx.ts index 4dcd5e4e6e6..839ee30151d 100644 --- a/packages/tx/src/4844/tx.ts +++ b/packages/tx/src/4844/tx.ts @@ -55,6 +55,10 @@ export type NetworkWrapperType = (typeof NetworkWrapperType)[keyof typeof Networ * * - TransactionType: 3 * - EIP: [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844) + * + * This tx type has two "modes": the plain canonical format only contains `blobVersionedHashes`. + * If blobs are passed in the tx automatically switches to "Network Wrapper" format and the + * `networkWrapperVersion` will be set or validated. */ export class Blob4844Tx implements TransactionInterface { public type = TransactionType.BlobEIP4844 // 4844 tx type @@ -76,12 +80,21 @@ export class Blob4844Tx implements TransactionInterface toType(blob, TypeOutput.PrefixedHexString)) + + if (this.networkWrapperVersion === undefined && this.blobs !== undefined) { + if (this.common.isActivatedEIP(7594)) { + this.networkWrapperVersion = 1 + } else { + this.networkWrapperVersion = 0 + } + } + if (this.networkWrapperVersion !== undefined && this.blobs === undefined) { + const msg = Legacy.errorMsg( + this, + 'tx is not allowed to be in network wrapper format if no blob list is provided', + ) + throw EthereumJSErrorWithoutCode(msg) + } + this.kzgCommitments = txData.kzgCommitments?.map((commitment) => toType(commitment, TypeOutput.PrefixedHexString), ) this.kzgProofs = txData.kzgProofs?.map((proof) => toType(proof, TypeOutput.PrefixedHexString)) + + if (this.blobs !== undefined) { + if (this.kzgCommitments === undefined) { + const msg = Legacy.errorMsg(this, 'kzgCommitments are mandatory if blobs are provided') + throw EthereumJSErrorWithoutCode(msg) + } + if (this.kzgProofs === undefined) { + const msg = Legacy.errorMsg(this, 'kzgProofs are mandatory if blobs are provided') + throw EthereumJSErrorWithoutCode(msg) + } + } + const freeze = opts?.freeze ?? true if (freeze) { Object.freeze(this) diff --git a/packages/tx/src/types.ts b/packages/tx/src/types.ts index ffc77583d51..cd044b19af3 100644 --- a/packages/tx/src/types.ts +++ b/packages/tx/src/types.ts @@ -402,7 +402,7 @@ export interface BlobEIP4844TxData extends FeeMarketEIP1559TxData { */ kzgCommitments?: BytesLike[] /** - * The KZG proofs associated with the transaction + * The KZG proofs associated with the transaction (EIP-4844: per-Blob proofs, EIP-7594: per-Cell proofs) */ kzgProofs?: BytesLike[] /** diff --git a/packages/tx/test/eip4844.spec.ts b/packages/tx/test/eip4844.spec.ts index 09b0c1f1479..45992bd62a9 100644 --- a/packages/tx/test/eip4844.spec.ts +++ b/packages/tx/test/eip4844.spec.ts @@ -12,7 +12,7 @@ import { randomBytes, } from '@ethereumjs/util' import { trustedSetup } from '@paulmillr/trusted-setups/fast-peerdas.js' -import { loadKZG } from 'kzg-wasm' +//import { loadKZG } from 'kzg-wasm' import { KZG as microEthKZG } from 'micro-eth-signer/kzg.js' import { assert, beforeAll, describe, it } from 'vitest' @@ -38,7 +38,7 @@ let kzgs: Array<{ lib: KZG; label: string; common: any }> = [] beforeAll(async () => { const jsKzg = new microEthKZG(trustedSetup) as KZG - const wasmKzg = (await loadKZG()) as KZG + //const wasmKzg = (await loadKZG()) as KZG const jsKzgSetup = { lib: jsKzg, @@ -49,7 +49,8 @@ beforeAll(async () => { customCrypto: { kzg: jsKzg }, }), } - const wasmKzgSetup = { + // Only activate on demand, otherwise too costly for now + /*const wasmKzgSetup = { lib: wasmKzg, label: 'WASM', common: createCommonFromGethGenesis(eip4844GethGenesis, { @@ -57,8 +58,8 @@ beforeAll(async () => { hardfork: Hardfork.Cancun, customCrypto: { kzg: wasmKzg }, }), - } - kzgs = [jsKzgSetup, wasmKzgSetup] + }*/ + kzgs = [jsKzgSetup] }, 50000) describe('EIP4844 addSignature tests', () => { @@ -436,57 +437,6 @@ describe('Network wrapper tests', () => { `throws on blobsData and blobs in txData (${kzg.label})`, ) - assert.throws( - () => - createBlob4844Tx( - { - blobsData: ['hello world'], - kzgCommitments: ['0xabcd'], - maxFeePerBlobGas: 100000000n, - gasLimit: 0xffffffn, - to: randomBytes(20), - }, - { common }, - ), - 'KZG commitments', - undefined, - `throws on blobsData and KZG commitments in txData (${kzg.label})`, - ) - - assert.throws( - () => - createBlob4844Tx( - { - blobsData: ['hello world'], - blobVersionedHashes: ['0x01cd'], - maxFeePerBlobGas: 100000000n, - gasLimit: 0xffffffn, - to: randomBytes(20), - }, - { common }, - ), - 'versioned hashes', - undefined, - `throws on blobsData and versioned hashes in txData (${kzg.label})`, - ) - - assert.throws( - () => - createBlob4844Tx( - { - blobsData: ['hello world'], - kzgProofs: ['0x01cd'], - maxFeePerBlobGas: 100000000n, - gasLimit: 0xffffffn, - to: randomBytes(20), - }, - { common }, - ), - 'KZG proofs', - undefined, - `throws on blobsData and KZG proofs in txData (${kzg.label})`, - ) - assert.throws( () => { createBlob4844Tx( diff --git a/packages/tx/test/eip7594.spec.ts b/packages/tx/test/eip7594.spec.ts index eca8f63ddc1..f2a8c1d13fa 100644 --- a/packages/tx/test/eip7594.spec.ts +++ b/packages/tx/test/eip7594.spec.ts @@ -1,8 +1,8 @@ import { Hardfork, createCommonFromGethGenesis } from '@ethereumjs/common' import { - blobsToCellsAndProofs, + CELLS_PER_EXT_BLOB, + blobsToCellProofs, blobsToCommitments, - blobsToProofs, bytesToHex, commitmentsToVersionedHashes, concatBytes, @@ -13,7 +13,7 @@ import { randomBytes, } from '@ethereumjs/util' import { trustedSetup } from '@paulmillr/trusted-setups/fast-peerdas.js' -import { loadKZG } from 'kzg-wasm' +//import { loadKZG } from 'kzg-wasm' import { KZG as microEthKZG } from 'micro-eth-signer/kzg.js' import { assert, beforeAll, describe, expect, it } from 'vitest' @@ -25,7 +25,6 @@ import { createBlob4844TxFromSerializedNetworkWrapper, createMinimal4844TxFromNetworkWrapper, createTx, - paramsTx, } from '../src/index.ts' import { osakaGethGenesis } from '@ethereumjs/testdata' @@ -38,28 +37,28 @@ let kzgs: Array<{ lib: KZG; label: string; common: any }> = [] beforeAll(async () => { const jsKzg = new microEthKZG(trustedSetup) as KZG - const wasmKzg = (await loadKZG()) as KZG - - kzgs = [ - { - lib: jsKzg, - label: 'JS', - common: createCommonFromGethGenesis(osakaGethGenesis.osakaGenesis, { - chain: 'customChain', - hardfork: Hardfork.Osaka, - customCrypto: { kzg: jsKzg }, - }), - }, - { - lib: wasmKzg, - label: 'WASM', - common: createCommonFromGethGenesis(osakaGethGenesis.osakaGenesis, { - chain: 'customChain', - hardfork: Hardfork.Osaka, - customCrypto: { kzg: wasmKzg }, - }), - }, - ] + //const wasmKzg = (await loadKZG()) as KZG + + const jsKzgSetup = { + lib: jsKzg, + label: 'JS', + common: createCommonFromGethGenesis(osakaGethGenesis.osakaGenesis, { + chain: 'customChain', + hardfork: Hardfork.Osaka, + customCrypto: { kzg: jsKzg }, + }), + } + // Only activate on demand, otherwise too costly for now + /*const wasmKzgSetup = { + lib: wasmKzg, + label: 'WASM', + common: createCommonFromGethGenesis(osakaGethGenesis.osakaGenesis, { + chain: 'customChain', + hardfork: Hardfork.Osaka, + customCrypto: { kzg: wasmKzg }, + }), + }*/ + kzgs = [jsKzgSetup] }, 50000) describe('EIP4844 non network wrapper constructor tests - valid scenarios', () => { @@ -185,36 +184,29 @@ describe('fromTxData using from a json', () => { }) describe('Network wrapper tests', () => { - it('eip7594 wrapper should not work instead of 4844', async () => { + it('should not work with wrong network wrapper version', async () => { for (const kzg of kzgs) { - const common = createCommonFromGethGenesis(osakaGethGenesis.osakaGenesis, { - chain: 'customChain', - hardfork: Hardfork.Osaka, - params: paramsTx, - customCrypto: { kzg: kzg.lib }, - }) - const blobs = [...getBlobs('hello world'), ...getBlobs('hello world')] - const commitments = blobsToCommitments(kzg.lib, blobs) - const blobVersionedHashes = commitmentsToVersionedHashes(commitments) - const blobProofs = blobsToProofs(kzg.lib, blobs, commitments) - - const [_cells, cellProofs, _indices] = blobsToCellsAndProofs(kzg.lib, blobs) - expect(() => createBlob4844Tx( { networkWrapperVersion: 0, - blobVersionedHashes, - blobs, - kzgCommitments: commitments, - kzgProofs: blobProofs, + blobs: getBlobs('hello world'), maxFeePerBlobGas: 100000000n, gasLimit: 0xffffffn, to: randomBytes(20), }, - { common }, + { common: kzg.common }, ), - ).toThrowError(/EIP-7594 is active on Common for EIP4844 network wrapper version/) + ).toThrowError(/EIP-7594 is active on Common for EIP-4844 network wrapper version/) + } + }, 40_000) + + it('should work with all data provided', async () => { + for (const kzg of kzgs) { + const blobs = [...getBlobs('hello world'), ...getBlobs('hello world')] + const commitments = blobsToCommitments(kzg.lib, blobs) + const blobVersionedHashes = commitmentsToVersionedHashes(commitments) + const cellProofs = blobsToCellProofs(kzg.lib, blobs) const unsignedTx = createBlob4844Tx( { @@ -227,7 +219,7 @@ describe('Network wrapper tests', () => { gasLimit: 0xffffffn, to: randomBytes(20), }, - { common }, + { common: kzg.common }, ) assert(unsignedTx.networkWrapperVersion === NetworkWrapperType.EIP7594) @@ -235,7 +227,7 @@ describe('Network wrapper tests', () => { const sender = signedTx.getSenderAddress().toString() const wrapper = signedTx.serializeNetworkWrapper() - const jsonData = blobTxNetworkWrapperToJSON(wrapper, { common }) + const jsonData = blobTxNetworkWrapperToJSON(wrapper, { common: kzg.common }) assert.equal( Number(jsonData.networkWrapperVersion), NetworkWrapperType.EIP7594, @@ -274,7 +266,7 @@ describe('Network wrapper tests', () => { } const deserializedTx = createBlob4844TxFromSerializedNetworkWrapper(wrapper, { - common, + common: kzg.common, }) assert.equal( @@ -308,7 +300,9 @@ describe('Network wrapper tests', () => { `decoded sender address correctly (${kzg.label})`, ) - const minimalTx = createMinimal4844TxFromNetworkWrapper(deserializedTx, { common }) + const minimalTx = createMinimal4844TxFromNetworkWrapper(deserializedTx, { + common: kzg.common, + }) assert.isUndefined(minimalTx.blobs, `minimal representation contains no blobs (${kzg.label})`) assert.isUndefined( minimalTx.networkWrapperVersion, @@ -330,7 +324,7 @@ describe('Network wrapper tests', () => { gasLimit: 0xffffffn, to: randomBytes(20), }, - { common }, + { common: kzg.common }, ) const serializedWithMissingBlob = txWithMissingBlob.serializeNetworkWrapper() @@ -338,7 +332,7 @@ describe('Network wrapper tests', () => { assert.throws( () => createBlob4844TxFromSerializedNetworkWrapper(serializedWithMissingBlob, { - common, + common: kzg.common, }), 'Number of blobVersionedHashes, blobs, and commitments not all equal', undefined, @@ -360,7 +354,7 @@ describe('Network wrapper tests', () => { gasLimit: 0xffffffn, to: randomBytes(20), }, - { common }, + { common: kzg.common }, ) const serializedWithInvalidCommitment = txWithInvalidCommitment.serializeNetworkWrapper() @@ -368,7 +362,7 @@ describe('Network wrapper tests', () => { assert.throws( () => createBlob4844TxFromSerializedNetworkWrapper(serializedWithInvalidCommitment, { - common, + common: kzg.common, }), 'KZG proof cannot be verified from blobs/commitments', undefined, @@ -376,4 +370,30 @@ describe('Network wrapper tests', () => { ) } }, 40_000) + + it('should calculate the correct cell proofs', async () => { + for (const kzg of kzgs) { + const tx = await createBlob4844Tx( + { + blobs: getBlobs('hello world'), + maxFeePerBlobGas: 100000000n, + gasLimit: 0xffffffn, + to: randomBytes(20), + }, + { common: kzg.common }, + ) + assert.equal( + tx.kzgProofs?.length, + CELLS_PER_EXT_BLOB * tx.blobs!.length, + `contains the correct number of cell proofs (${kzg.label})`, + ) + const expectedCellProof0 = + '0x96a250543b1e967a04e0e4b7b95972af79d0ca074e813e3b1c5a150bae28a0d4e9e3c52264ee103b140b662de514a184' + assert.equal( + tx.kzgProofs![0], + expectedCellProof0, + `contains the correct cell proof (${kzg.label})`, + ) + } + }, 40_000) }) diff --git a/packages/util/src/blobs.ts b/packages/util/src/blobs.ts index 254f655b8f1..87d91e611a2 100644 --- a/packages/util/src/blobs.ts +++ b/packages/util/src/blobs.ts @@ -197,3 +197,13 @@ export const blobsToCellsAndProofs = ( const indices = Array.from({ length: CELLS_PER_EXT_BLOB }, (_, i) => i) return [...blobsAndCells, indices] as [PrefixedHexString[], PrefixedHexString[], number[]] } + +/** + * EIP-7594: Computes cell proofs for the given blobs. + * @param kzg KZG implementation capable of computing cell proofs + * @param blobs Array of blob data as hex-prefixed strings + * @returns Array of lowercase hex-prefixed cell proofs (aligned with input order) + */ +export const blobsToCellProofs = (kzg: KZG, blobs: PrefixedHexString[]): PrefixedHexString[] => { + return blobsToCellsAndProofs(kzg, blobs)[1] as PrefixedHexString[] +} diff --git a/packages/util/test/bench/kzg.bench.ts b/packages/util/test/bench/kzg.bench.ts index 852bfebe437..2100456eee1 100644 --- a/packages/util/test/bench/kzg.bench.ts +++ b/packages/util/test/bench/kzg.bench.ts @@ -2,7 +2,10 @@ import { getBlobs } from '@ethereumjs/util' import { loadKZG } from 'kzg-wasm' import { bench, describe } from 'vitest' -import { jsKZG } from '../kzg.spec.ts' +import { trustedSetup } from '@paulmillr/trusted-setups/fast-peerdas.js' +import { KZG as microEthKZG } from 'micro-eth-signer/kzg.js' + +const jsKZG = new microEthKZG(trustedSetup) /** * These benchmarks compare performance of various KZG related functions for our two supported backends diff --git a/packages/util/test/blobs.spec.ts b/packages/util/test/blobs.spec.ts index 61e947f6649..ba0e7c417b9 100644 --- a/packages/util/test/blobs.spec.ts +++ b/packages/util/test/blobs.spec.ts @@ -1,12 +1,20 @@ import { assert, describe, it } from 'vitest' +import { trustedSetup } from '@paulmillr/trusted-setups/fast-peerdas.js' +import { KZG as microEthKZG } from 'micro-eth-signer/kzg.js' import { + blobsToCellProofs, + blobsToCellsAndProofs, + blobsToCommitments, + blobsToProofs, bytesToHex, commitmentsToVersionedHashes, computeVersionedHash, getBlobs, } from '../src/index.ts' +const kzg = new microEthKZG(trustedSetup) + describe('getBlobs()', () => { it('should return an array of PrefixedHexString blobs', () => { const input = 'test input' @@ -59,3 +67,50 @@ describe('commitmentsToVersionedHashes()', () => { } }) }) + +describe('blobsToProofs()', () => { + it('should return an array of proofs', () => { + const blobs = getBlobs(['0x01', '0x02']) + const commitments = blobsToCommitments(kzg, blobs) + const proofs = blobsToProofs(kzg, blobs, commitments) + assert(Array.isArray(proofs)) + + const expectedProofs = [ + '0x98994b1a2921d0eb16efc1fdc7f1edc0cecbe1d88194a5c2db6608996ebf20458c4230ab11e16863a9eefa02f654d5fa', + '0x93a3260ab9a0fa5b7287f9b21397cf141595aada6f4a5a2c0876858a3df039ad7540b58b6e472fe19f3d40f19605e94f', + ] + assert.deepEqual(proofs, expectedProofs) + assert.lengthOf(proofs, 2) + }) +}) + +describe('blobsToCellsAndProofs()', () => { + it('should return an array of cells and proofs', () => { + const blobs = getBlobs(['0x01', '0x02']) + const cellsAndProofs = blobsToCellsAndProofs(kzg, blobs) + assert(Array.isArray(cellsAndProofs)) + + const expectedCell0 = + '0x0000000000000000000000000000000000000000000000000000008031307830000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + const expectedProof0 = + '0x825ce2ddc755c5b43974ece42fb2fad0b6105019f10b7dd98a1330b01019a2f3d9d6daf08c9958858dc2ee0086ca106c' + const expectedIndex0 = 0 + assert.deepEqual(cellsAndProofs[0][0], expectedCell0) + assert.deepEqual(cellsAndProofs[1][0], expectedProof0) + assert.deepEqual(cellsAndProofs[2][0], expectedIndex0) + assert.lengthOf(cellsAndProofs, 3) + }, 40000) +}) + +describe('blobsToCellProofs()', () => { + it('should return an array of cell proofs', () => { + const blobs = getBlobs(['0x01', '0x02']) + const proofs = blobsToCellProofs(kzg, blobs) + assert(Array.isArray(proofs)) + + const expectedProof0 = + '0x825ce2ddc755c5b43974ece42fb2fad0b6105019f10b7dd98a1330b01019a2f3d9d6daf08c9958858dc2ee0086ca106c' + assert.deepEqual(proofs[0], expectedProof0) + assert.lengthOf(proofs, 256) + }, 40000) +}) diff --git a/packages/util/test/kzg.spec.ts b/packages/util/test/kzg.spec.ts index 477d1bcd887..6aebc56a8d6 100644 --- a/packages/util/test/kzg.spec.ts +++ b/packages/util/test/kzg.spec.ts @@ -7,7 +7,7 @@ import { getBlobs } from '../src/blobs.ts' import type { KZG } from '../src/kzg.ts' -export const jsKZG = new microEthKZG(trustedSetup) +const jsKZG = new microEthKZG(trustedSetup) describe('KZG API tests', () => { let wasmKZG: KZG