diff --git a/.changeset/eip7594-peerdas-support.md b/.changeset/eip7594-peerdas-support.md new file mode 100644 index 0000000000..352465be9f --- /dev/null +++ b/.changeset/eip7594-peerdas-support.md @@ -0,0 +1,35 @@ +--- +"viem": minor +--- + +Added EIP-7594 (PeerDAS) blob support. + +**Breaking Changes** + +`blobsToProofs` now returns an array of arrays (`ByteArray[][] | Hex[][]`) instead of a flat array. Each blob gets its own array of proofs to support EIP-7594's cell proofs (128 proofs per blob). + +```ts +// Before (EIP-4844 only) +const proofs = blobsToProofs({ blobs, commitments, kzg }) +// proofs = [proof1, proof2, ...] + +// After (supports both EIP-4844 and EIP-7594) +const proofs = blobsToProofs({ blobs, commitments, kzg, blobVersion: '7594' }) +// EIP-4844: proofs = [[proof1], [proof2], ...] +// EIP-7594: proofs = [[proof1, ...proof128], [proof129, ...proof256], ...] +``` + +**Features** + +- Added EIP-7594 blob transaction support with 128 cell proofs per blob +- Automatic blob version detection based on chain ID (Sepolia uses EIP-7594) +- `parseTransaction` now handles 5-element EIP-7594 wrapper arrays with version byte +- `BlobSidecar.proof` type updated to support both single proof and proof arrays +- `serializeTransaction` correctly flattens proof arrays for both EIP standards + +**Implementation** + +- Updated `blobsToProofs` to use `computeCellsAndKzgProofs` for EIP-7594 +- Enhanced `parseTransaction` to detect and parse EIP-7594 wrapper format +- Modified `toBlobSidecars` to handle variable-length proof arrays +- Added chain-based blob version detection (Sepolia = EIP-7594, others = EIP-4844) diff --git a/package.json b/package.json index 7b02961881..b493bdda41 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "@changesets/changelog-github": "^0.4.8", "@changesets/cli": "^2.29.7", "@ethereumjs/rlp": "^5.0.2", - "@paulmillr/trusted-setups": "^0.1.2", + "@paulmillr/trusted-setups": "^0.3.0", "@pimlico/alto": "0.0.18", "@size-limit/preset-big-lib": "^11.2.0", "@types/bun": "^1.2.22", @@ -63,7 +63,7 @@ "ethers": "^6.15.0", "glob": "^10.4.5", "knip": "^5.64.0", - "micro-eth-signer": "^0.14.0", + "micro-eth-signer": "^0.17.3", "permissionless": "^0.2.57", "prool": "0.0.24", "publint": "^0.2.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb68f5db48..784e4ad355 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -67,8 +67,8 @@ importers: specifier: ^5.0.2 version: 5.0.2 '@paulmillr/trusted-setups': - specifier: ^0.1.2 - version: 0.1.2 + specifier: ^0.3.0 + version: 0.3.0 '@pimlico/alto': specifier: 0.0.18 version: 0.0.18(typescript@5.9.2) @@ -100,8 +100,8 @@ importers: specifier: ^5.64.0 version: 5.64.0(@types/node@24.5.2)(typescript@5.9.2) micro-eth-signer: - specifier: ^0.14.0 - version: 0.14.0 + specifier: ^0.17.3 + version: 0.17.3 permissionless: specifier: ^0.2.57 version: 0.2.57 @@ -566,7 +566,7 @@ importers: version: link:../src vocs: specifier: ^1.0.11 - version: 1.0.11(@types/node@24.5.2)(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(acorn@8.15.0)(jiti@2.6.0)(lightningcss@1.29.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.52.2)(terser@5.36.0)(typescript@5.9.2)(yaml@2.7.0) + version: 1.0.11(@types/node@24.5.2)(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(acorn@8.15.0)(jiti@2.6.0)(lightningcss@1.29.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.52.2)(terser@5.36.0)(typescript@5.9.3)(yaml@2.7.0) src: dependencies: @@ -1448,26 +1448,26 @@ packages: '@noble/curves@1.2.0': resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} - '@noble/curves@1.8.2': - resolution: {integrity: sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==} - engines: {node: ^14.21.3 || >=16} - '@noble/curves@1.9.1': resolution: {integrity: sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==} engines: {node: ^14.21.3 || >=16} + '@noble/curves@2.0.1': + resolution: {integrity: sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==} + engines: {node: '>= 20.19.0'} + '@noble/hashes@1.3.2': resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} engines: {node: '>= 16'} - '@noble/hashes@1.7.2': - resolution: {integrity: sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==} - engines: {node: ^14.21.3 || >=16} - '@noble/hashes@1.8.0': resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} engines: {node: ^14.21.3 || >=16} + '@noble/hashes@2.0.1': + resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} + engines: {node: '>= 20.19.0'} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -1830,8 +1830,8 @@ packages: cpu: [x64] os: [win32] - '@paulmillr/trusted-setups@0.1.2': - resolution: {integrity: sha512-NKpT0G4Blj+Vp9lbfczb7iPJm3B4njpzX8NXiQs8G51N4CLndCvLqEvksFlTJbNLwl3iwsGZHSvJRdfhfOHLwQ==} + '@paulmillr/trusted-setups@0.3.0': + resolution: {integrity: sha512-j752FHV8nYS8xG7msHj0kkakeeh/gUgcJM48i0sDihMcbHK1bmP2ZeObqyausF1NhfkDYkx5t30yKAuLQIWXog==} '@pimlico/alto@0.0.18': resolution: {integrity: sha512-JIDEEYgdnkT7+wdxk0OBLSVwhm2CaLSbCw4474C9ZFmBggKBOByzaYCeIAJPb+Tag3WVBDXrXb2lYi2aRT9phQ==} @@ -2684,6 +2684,9 @@ packages: '@scure/base@1.2.5': resolution: {integrity: sha512-9rE6EOVeIQzt5TSu4v+K523F8u6DhBsoZWPGKlnCshhlDhy0kJzUX4V+tr2dWmzF1GdekvThABoEQBGBQI7xZw==} + '@scure/base@2.0.0': + resolution: {integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==} + '@scure/bip32@1.7.0': resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} @@ -4700,11 +4703,13 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - micro-eth-signer@0.14.0: - resolution: {integrity: sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw==} + micro-eth-signer@0.17.3: + resolution: {integrity: sha512-6NSgzeSAO2oRQmzH3SWPS+/abXrO09E8i4utmufV1O1oez/C1E2hNpeOX/OFOHJghAS7XzPm9tkh625v/49TwA==} + engines: {node: '>= 20.19.0'} - micro-packed@0.7.2: - resolution: {integrity: sha512-HJ/u8+tMzgrJVAl6P/4l8KGjJSA3SCZaRb1m4wpbovNScCSmVOGUYbkkcoPPcknCHWPpRAdjy+yqXqyQWf+k8g==} + micro-packed@0.8.0: + resolution: {integrity: sha512-AKb8znIvg9sooythbXzyFeChEY0SkW0C6iXECpy/ls0e5BtwXO45J9wD9SLzBztnS4XmF/5kwZknsq+jyynd/A==} + engines: {node: '>= 20.19.0'} micromark-core-commonmark@2.0.2: resolution: {integrity: sha512-FKjQKbxd1cibWMM1P9N+H8TwlgGgSkWZMmfuVucLCHaYqeSvJ0hFeHsIa65pA2nYbes0f8LDHPMrd9X7Ujxg9w==} @@ -7364,20 +7369,20 @@ snapshots: dependencies: '@noble/hashes': 1.3.2 - '@noble/curves@1.8.2': - dependencies: - '@noble/hashes': 1.7.2 - '@noble/curves@1.9.1': dependencies: '@noble/hashes': 1.8.0 - '@noble/hashes@1.3.2': {} + '@noble/curves@2.0.1': + dependencies: + '@noble/hashes': 2.0.1 - '@noble/hashes@1.7.2': {} + '@noble/hashes@1.3.2': {} '@noble/hashes@1.8.0': {} + '@noble/hashes@2.0.1': {} + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 @@ -7731,7 +7736,7 @@ snapshots: '@oxc-resolver/binding-win32-x64-msvc@11.8.3': optional: true - '@paulmillr/trusted-setups@0.1.2': {} + '@paulmillr/trusted-setups@0.3.0': {} '@pimlico/alto@0.0.18(typescript@5.9.2)': dependencies: @@ -8622,6 +8627,8 @@ snapshots: '@scure/base@1.2.5': {} + '@scure/base@2.0.0': {} + '@scure/bip32@1.7.0': dependencies: '@noble/curves': 1.9.1 @@ -8709,11 +8716,11 @@ snapshots: '@shikijs/core': 1.29.2 '@shikijs/types': 1.29.2 - '@shikijs/twoslash@1.29.2(typescript@5.9.2)': + '@shikijs/twoslash@1.29.2(typescript@5.9.3)': dependencies: '@shikijs/core': 1.29.2 '@shikijs/types': 1.29.2 - twoslash: 0.2.12(typescript@5.9.2) + twoslash: 0.2.12(typescript@5.9.3) transitivePeerDependencies: - supports-color - typescript @@ -8982,10 +8989,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript/vfs@1.6.1(typescript@5.9.2)': + '@typescript/vfs@1.6.1(typescript@5.9.3)': dependencies: debug: 4.4.0 - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -10985,15 +10992,15 @@ snapshots: merge2@1.4.1: {} - micro-eth-signer@0.14.0: + micro-eth-signer@0.17.3: dependencies: - '@noble/curves': 1.8.2 - '@noble/hashes': 1.7.2 - micro-packed: 0.7.2 + '@noble/curves': 2.0.1 + '@noble/hashes': 2.0.1 + micro-packed: 0.8.0 - micro-packed@0.7.2: + micro-packed@0.8.0: dependencies: - '@scure/base': 1.2.5 + '@scure/base': 2.0.0 micromark-core-commonmark@2.0.2: dependencies: @@ -12691,11 +12698,11 @@ snapshots: twoslash-protocol@0.2.12: {} - twoslash@0.2.12(typescript@5.9.2): + twoslash@0.2.12(typescript@5.9.3): dependencies: - '@typescript/vfs': 1.6.1(typescript@5.9.2) + '@typescript/vfs': 1.6.1(typescript@5.9.3) twoslash-protocol: 0.2.12 - typescript: 5.9.2 + typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -13000,7 +13007,7 @@ snapshots: - tsx - yaml - vocs@1.0.11(@types/node@24.5.2)(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(acorn@8.15.0)(jiti@2.6.0)(lightningcss@1.29.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.52.2)(terser@5.36.0)(typescript@5.9.2)(yaml@2.7.0): + vocs@1.0.11(@types/node@24.5.2)(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(acorn@8.15.0)(jiti@2.6.0)(lightningcss@1.29.1)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)(rollup@4.52.2)(terser@5.36.0)(typescript@5.9.3)(yaml@2.7.0): dependencies: '@floating-ui/react': 0.27.4(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@hono/node-server': 1.13.8(hono@4.9.7) @@ -13017,7 +13024,7 @@ snapshots: '@radix-ui/react-tabs': 1.1.3(@types/react-dom@19.0.3(@types/react@19.0.8))(@types/react@19.0.8)(react-dom@19.0.0(react@19.0.0))(react@19.0.0) '@shikijs/rehype': 1.29.2 '@shikijs/transformers': 1.29.2 - '@shikijs/twoslash': 1.29.2(typescript@5.9.2) + '@shikijs/twoslash': 1.29.2(typescript@5.9.3) '@tailwindcss/vite': 4.0.7(vite@6.3.6(@types/node@24.5.2)(jiti@2.6.0)(lightningcss@1.29.1)(terser@5.36.0)(yaml@2.7.0)) '@vanilla-extract/css': 1.17.1 '@vanilla-extract/dynamic': 2.1.2 @@ -13065,7 +13072,7 @@ snapshots: serve-static: 1.16.2 shiki: 1.29.2 toml: 3.0.0 - twoslash: 0.2.12(typescript@5.9.2) + twoslash: 0.2.12(typescript@5.9.3) ua-parser-js: 1.0.40 unified: 11.0.5 unist-util-visit: 5.0.0 diff --git a/site/pages/docs/guides/blob-transactions.md b/site/pages/docs/guides/blob-transactions.md index c9f516b7cd..f30d011014 100644 --- a/site/pages/docs/guides/blob-transactions.md +++ b/site/pages/docs/guides/blob-transactions.md @@ -2,9 +2,12 @@ Blob Transactions are a new type of transaction in Ethereum (introduced in [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)) that allows you to broadcast BLObs (Binary Large Objects) to the Ethereum network. Blob Transactions are like any other transaction, but with the added ability to carry a payload of Blobs. Blobs are extremely larger than regular calldata (~128kB), however unlike regular calldata, they are not accessible on the EVM. The EVM can only view the commitments of the blobs. Blobs are also transient, and only last for 4096 epochs (approx. 18 days). -To read more on Blob Transactions and EIP-4844, check out these resources: +Viem also supports [EIP-7594](https://eips.ethereum.org/EIPS/eip-7594) (PeerDAS) blobs, which use a different proof format. On Sepolia, EIP-7594 is automatically used, while other chains default to EIP-4844. + +To read more on Blob Transactions and EIP-4844, check out these resources: - [EIP-4844 Spec](https://eips.ethereum.org/EIPS/eip-4844) +- [EIP-7594 Spec](https://eips.ethereum.org/EIPS/eip-7594) - [EIP-4844 Website](https://www.eip4844.com/#faq) - [EIP-4844 FAQ](https://notes.ethereum.org/@vbuterin/proto_danksharding_faq#Proto-Danksharding-FAQ) diff --git a/site/pages/docs/utilities/blobsToProofs.md b/site/pages/docs/utilities/blobsToProofs.md index c56eab4c1e..7d2d56b920 100644 --- a/site/pages/docs/utilities/blobsToProofs.md +++ b/site/pages/docs/utilities/blobsToProofs.md @@ -99,9 +99,31 @@ const blobs = toBlobs({ data: '0x...' }) const kzg = setupKzg(cKzg, mainnetTrustedSetupPath) // [!code focus] const commitments = blobsToCommitments({ blobs, kzg }) -const proofs = blobsToProofs({ +const proofs = blobsToProofs({ blobs, commitments, kzg, // [!code focus] -}) +}) +``` + +### blobVersion (optional) + +- **Type:** `'4844' | '7594'` +- **Default:** `'4844'` + +The blob version to use for proof generation. Defaults to `'4844'` (EIP-4844). Use `'7594'` for EIP-7594 (PeerDAS) blobs. + +```ts twoslash +import { blobsToCommitments, blobsToProofs, toBlobs } from 'viem' +import { kzg } from './kzg' + +const blobs = toBlobs({ data: '0x...' }) +const commitments = blobsToCommitments({ blobs, kzg }) + +const proofs = blobsToProofs({ + blobs, + commitments, + kzg, + blobVersion: '7594', // [!code focus] +}) ``` diff --git a/site/pages/docs/utilities/toBlobSidecars.md b/site/pages/docs/utilities/toBlobSidecars.md index 05a82f5ea2..33207dc2f5 100644 --- a/site/pages/docs/utilities/toBlobSidecars.md +++ b/site/pages/docs/utilities/toBlobSidecars.md @@ -205,14 +205,32 @@ const kzg = defineKzg({} as any) // ---cut--- import { toBlobSidecars, toBlobs } from 'viem' -const sidecars = toBlobSidecars({ +const sidecars = toBlobSidecars({ data: '0x1234', - kzg, - to: 'bytes', // [!code focus] -}) + kzg, + to: 'bytes', // [!code focus] +}) sidecars // [!code focus] // ^? +``` + +### blobVersion (optional) + +- **Type:** `'4844' | '7594'` +- **Default:** `'4844'` + +The blob version to use for proof generation. Defaults to `'4844'` (EIP-4844). Use `'7594'` for EIP-7594 (PeerDAS) blobs. + +```ts twoslash +import { toBlobSidecars } from 'viem' +import { kzg } from './kzg' + +const sidecars = toBlobSidecars({ + data: '0x...', + kzg, + blobVersion: '7594', // [!code focus] +}) ``` \ No newline at end of file diff --git a/src/actions/wallet/sendRawTransaction.test.ts b/src/actions/wallet/sendRawTransaction.test.ts index 144059a994..52bdffc817 100644 --- a/src/actions/wallet/sendRawTransaction.test.ts +++ b/src/actions/wallet/sendRawTransaction.test.ts @@ -27,10 +27,10 @@ test('default', async () => { test.skip('4844', async () => { const client = createClient({ chain: sepolia, - transport: http('https://ethereum-sepolia-rpc.publicnode.com'), + transport: http(process.env.RPC_URL!), }) - const privateKey = '0x' + const privateKey = process.env.PRIVATE_KEY as `0x${string}` const account = privateKeyToAccount(privateKey) const blobs = toBlobs({ data: stringToHex(blobData) }) const nonce = await getTransactionCount(client, { diff --git a/src/index.ts b/src/index.ts index 700aee871f..a4914164b1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1410,6 +1410,12 @@ export { type FromBlobsReturnType, fromBlobs, } from './utils/blob/fromBlobs.js' +export { + type GetBlobVersionErrorType, + type GetBlobVersionParameters, + type GetBlobVersionReturnType, + getBlobVersion, +} from './utils/blob/getBlobVersion.js' export { type SidecarsToVersionedHashesErrorType, type SidecarsToVersionedHashesParameters, diff --git a/src/types/eip4844.ts b/src/types/eip4844.ts index 3eb6074619..c47a9b4d14 100644 --- a/src/types/eip4844.ts +++ b/src/types/eip4844.ts @@ -5,8 +5,12 @@ export type BlobSidecar = { blob: type /** The KZG commitment corresponding to this blob. */ commitment: type - /** The KZG proof corresponding to this blob and commitment. */ - proof: type + /** + * The KZG proof(s) corresponding to this blob and commitment. + * - EIP-4844: Single proof + * - EIP-7594: Array of cell proofs (128 proofs) + */ + proof: type | type[] } export type BlobSidecars = BlobSidecar[] diff --git a/src/types/kzg.ts b/src/types/kzg.ts index bac72235b4..74f206ffae 100644 --- a/src/types/kzg.ts +++ b/src/types/kzg.ts @@ -15,6 +15,11 @@ export type Kzg = { * commitment. */ computeBlobKzgProof(blob: ByteArray, commitment: ByteArray): ByteArray + /** + * Compute cells and their KZG proofs for a blob (EIP-7594). + * Returns a tuple of [cells, proofs]. + */ + computeCellsAndKzgProofs?(blob: ByteArray): [ByteArray[], ByteArray[]] } export type GetTransactionRequestKzgParameter< diff --git a/src/types/transaction.ts b/src/types/transaction.ts index 9f7b31265b..a3414d8d03 100644 --- a/src/types/transaction.ts +++ b/src/types/transaction.ts @@ -376,6 +376,8 @@ export type TransactionSerializableEIP4844< | (nullableSidecars extends true ? false : never) | undefined type?: 'eip4844' | undefined + /** Blob version (EIP-4844 or EIP-7594). Defaults to '4844'. */ + blobVersion?: '4844' | '7594' | undefined yParity?: number | undefined } & OneOf< | { diff --git a/src/utils/blob/blobsToProofs.test.ts b/src/utils/blob/blobsToProofs.test.ts index 8f87326e51..dde88323a8 100644 --- a/src/utils/blob/blobsToProofs.test.ts +++ b/src/utils/blob/blobsToProofs.test.ts @@ -1,122 +1,148 @@ import { expect, test } from 'vitest' import { blobData, kzg } from '../../../test/src/kzg.js' +import type { Kzg } from '../../types/kzg.js' +import type { ByteArray } from '../../types/misc.js' import { stringToBytes, stringToHex } from '../index.js' import { blobsToCommitments } from './blobsToCommitments.js' import { blobsToProofs } from './blobsToProofs.js' import { toBlobs } from './toBlobs.js' +// Mock KZG with EIP-7594 support +const kzg7594: Kzg = { + ...kzg, + computeCellsAndKzgProofs(_blob: ByteArray): [ByteArray[], ByteArray[]] { + // Mock implementation: generate 2 mock cell proofs for testing + const mockProof1 = new Uint8Array(48).fill(1) + const mockProof2 = new Uint8Array(48).fill(2) + const mockCell1 = new Uint8Array(2048).fill(0xa) + const mockCell2 = new Uint8Array(2048).fill(0xb) + return [ + [mockCell1, mockCell2], + [mockProof1, mockProof2], + ] + }, +} + test('from hex', () => { const blobs = toBlobs({ data: stringToHex(blobData) }) const commitments = blobsToCommitments({ blobs, kzg }) expect(blobsToProofs({ blobs, commitments, kzg })).toMatchInlineSnapshot(` [ - "0x91a6c5d19e50b1b85ae2ef07477160381babf00f0906f5219ce09dee2e00d7d347cb0586d90b491637cdb1715e62d152", - "0xa660592b94033f9c5f7987005fa5d1f84435585ddaaf4b3adc0a198b983f2ae007db73b90067a96ec214b24d7b9820b9", + [ + "0x91a6c5d19e50b1b85ae2ef07477160381babf00f0906f5219ce09dee2e00d7d347cb0586d90b491637cdb1715e62d152", + ], + [ + "0xa660592b94033f9c5f7987005fa5d1f84435585ddaaf4b3adc0a198b983f2ae007db73b90067a96ec214b24d7b9820b9", + ], ] `) expect( blobsToProofs({ blobs, commitments, kzg, to: 'bytes' }), ).toMatchInlineSnapshot(` [ - Uint8Array [ - 145, - 166, - 197, - 209, - 158, - 80, - 177, - 184, - 90, - 226, - 239, - 7, - 71, - 113, - 96, - 56, - 27, - 171, - 240, - 15, - 9, - 6, - 245, - 33, - 156, - 224, - 157, - 238, - 46, - 0, - 215, - 211, - 71, - 203, - 5, - 134, - 217, - 11, - 73, - 22, - 55, - 205, - 177, - 113, - 94, - 98, - 209, - 82, + [ + Uint8Array [ + 145, + 166, + 197, + 209, + 158, + 80, + 177, + 184, + 90, + 226, + 239, + 7, + 71, + 113, + 96, + 56, + 27, + 171, + 240, + 15, + 9, + 6, + 245, + 33, + 156, + 224, + 157, + 238, + 46, + 0, + 215, + 211, + 71, + 203, + 5, + 134, + 217, + 11, + 73, + 22, + 55, + 205, + 177, + 113, + 94, + 98, + 209, + 82, + ], ], - Uint8Array [ - 166, - 96, - 89, - 43, - 148, - 3, - 63, - 156, - 95, - 121, - 135, - 0, - 95, - 165, - 209, - 248, - 68, - 53, - 88, - 93, - 218, - 175, - 75, - 58, - 220, - 10, - 25, - 139, - 152, - 63, - 42, - 224, - 7, - 219, - 115, - 185, - 0, - 103, - 169, - 110, - 194, - 20, - 178, - 77, - 123, - 152, - 32, - 185, + [ + Uint8Array [ + 166, + 96, + 89, + 43, + 148, + 3, + 63, + 156, + 95, + 121, + 135, + 0, + 95, + 165, + 209, + 248, + 68, + 53, + 88, + 93, + 218, + 175, + 75, + 58, + 220, + 10, + 25, + 139, + 152, + 63, + 42, + 224, + 7, + 219, + 115, + 185, + 0, + 103, + 169, + 110, + 194, + 20, + 178, + 77, + 123, + 152, + 32, + 185, + ], ], ] `) @@ -127,105 +153,109 @@ test('from bytes', () => { const commitments = blobsToCommitments({ blobs, kzg }) expect(blobsToProofs({ blobs, commitments, kzg })).toMatchInlineSnapshot(` [ - Uint8Array [ - 145, - 166, - 197, - 209, - 158, - 80, - 177, - 184, - 90, - 226, - 239, - 7, - 71, - 113, - 96, - 56, - 27, - 171, - 240, - 15, - 9, - 6, - 245, - 33, - 156, - 224, - 157, - 238, - 46, - 0, - 215, - 211, - 71, - 203, - 5, - 134, - 217, - 11, - 73, - 22, - 55, - 205, - 177, - 113, - 94, - 98, - 209, - 82, + [ + Uint8Array [ + 145, + 166, + 197, + 209, + 158, + 80, + 177, + 184, + 90, + 226, + 239, + 7, + 71, + 113, + 96, + 56, + 27, + 171, + 240, + 15, + 9, + 6, + 245, + 33, + 156, + 224, + 157, + 238, + 46, + 0, + 215, + 211, + 71, + 203, + 5, + 134, + 217, + 11, + 73, + 22, + 55, + 205, + 177, + 113, + 94, + 98, + 209, + 82, + ], ], - Uint8Array [ - 166, - 96, - 89, - 43, - 148, - 3, - 63, - 156, - 95, - 121, - 135, - 0, - 95, - 165, - 209, - 248, - 68, - 53, - 88, - 93, - 218, - 175, - 75, - 58, - 220, - 10, - 25, - 139, - 152, - 63, - 42, - 224, - 7, - 219, - 115, - 185, - 0, - 103, - 169, - 110, - 194, - 20, - 178, - 77, - 123, - 152, - 32, - 185, + [ + Uint8Array [ + 166, + 96, + 89, + 43, + 148, + 3, + 63, + 156, + 95, + 121, + 135, + 0, + 95, + 165, + 209, + 248, + 68, + 53, + 88, + 93, + 218, + 175, + 75, + 58, + 220, + 10, + 25, + 139, + 152, + 63, + 42, + 224, + 7, + 219, + 115, + 185, + 0, + 103, + 169, + 110, + 194, + 20, + 178, + 77, + 123, + 152, + 32, + 185, + ], ], ] `) @@ -233,8 +263,69 @@ test('from bytes', () => { blobsToProofs({ blobs, commitments, kzg, to: 'hex' }), ).toMatchInlineSnapshot(` [ - "0x91a6c5d19e50b1b85ae2ef07477160381babf00f0906f5219ce09dee2e00d7d347cb0586d90b491637cdb1715e62d152", - "0xa660592b94033f9c5f7987005fa5d1f84435585ddaaf4b3adc0a198b983f2ae007db73b90067a96ec214b24d7b9820b9", + [ + "0x91a6c5d19e50b1b85ae2ef07477160381babf00f0906f5219ce09dee2e00d7d347cb0586d90b491637cdb1715e62d152", + ], + [ + "0xa660592b94033f9c5f7987005fa5d1f84435585ddaaf4b3adc0a198b983f2ae007db73b90067a96ec214b24d7b9820b9", + ], ] `) }) + +test('EIP-7594: from hex', () => { + const blobs = toBlobs({ data: stringToHex(blobData) }) + const commitments = blobsToCommitments({ blobs, kzg: kzg7594 }) + const proofs = blobsToProofs({ + blobs, + commitments, + kzg: kzg7594, + blobVersion: '7594', + }) + + // EIP-7594 should produce cell proofs (2 per blob for the mock, 2 blobs = 2 arrays) + expect(proofs.length).toBe(2) // 2 blobs + expect(proofs[0].length).toBe(2) // 2 proofs per blob (from mock) + // Each proof should be a hex string (from hex blobs) + expect(typeof proofs[0][0]).toBe('string') + expect(proofs[0][0]).toMatch(/^0x/) +}) + +test('EIP-7594: from bytes', () => { + const blobs = toBlobs({ data: stringToBytes(blobData) }) + const commitments = blobsToCommitments({ blobs, kzg: kzg7594 }) + const proofs = blobsToProofs({ + blobs, + commitments, + kzg: kzg7594, + blobVersion: '7594', + to: 'hex', + }) + + // EIP-7594 should produce cell proofs (2 per blob for the mock, 2 blobs = 2 arrays) + expect(proofs.length).toBe(2) // 2 blobs + expect(proofs[0].length).toBe(2) // 2 proofs per blob (from mock) + expect(typeof proofs[0][0]).toBe('string') + expect(proofs[0][0]).toMatch(/^0x/) +}) + +test('EIP-7594: throws error if computeCellsAndKzgProofs not available', () => { + const blobs = toBlobs({ data: stringToHex(blobData) }) + const commitments = blobsToCommitments({ blobs, kzg }) + + // Create a KZG object without computeCellsAndKzgProofs + const kzgWithout7594: Kzg = { + blobToKzgCommitment: kzg.blobToKzgCommitment, + computeBlobKzgProof: kzg.computeBlobKzgProof, + // No computeCellsAndKzgProofs method + } + + expect(() => + blobsToProofs({ + blobs, + commitments, + kzg: kzgWithout7594, + blobVersion: '7594', + }), + ).toThrowError(/does not support computeCellsAndKzgProofs/) +}) diff --git a/src/utils/blob/blobsToProofs.ts b/src/utils/blob/blobsToProofs.ts index da4eab1796..64cece4ce4 100644 --- a/src/utils/blob/blobsToProofs.ts +++ b/src/utils/blob/blobsToProofs.ts @@ -6,6 +6,8 @@ import { type BytesToHexErrorType, bytesToHex } from '../encoding/toHex.js' type To = 'hex' | 'bytes' +export type BlobVersion = '4844' | '7594' + export type blobsToProofsParameters< blobs extends readonly ByteArray[] | readonly Hex[], commitments extends readonly ByteArray[] | readonly Hex[], @@ -25,14 +27,16 @@ export type blobsToProofsParameters< ? {} : `commitments must be the same type as blobs`) /** KZG implementation. */ - kzg: Pick + kzg: Pick /** Return type. */ to?: to | To | undefined + /** Blob version (EIP-4844 or EIP-7594). Defaults to '4844'. */ + blobVersion?: BlobVersion | undefined } export type blobsToProofsReturnType = - | (to extends 'bytes' ? ByteArray[] : never) - | (to extends 'hex' ? Hex[] : never) + | (to extends 'bytes' ? ByteArray[][] : never) + | (to extends 'hex' ? Hex[][] : never) export type blobsToProofsErrorType = | BytesToHexErrorType @@ -42,6 +46,10 @@ export type blobsToProofsErrorType = /** * Compute the proofs for a list of blobs and their commitments. * + * Returns an array of proof arrays, where each inner array contains the proofs for one blob: + * - EIP-4844: Each blob has 1 proof + * - EIP-7594: Each blob has 128 cell proofs + * * @example * ```ts * import { @@ -53,6 +61,22 @@ export type blobsToProofsErrorType = * const blobs = toBlobs({ data: '0x1234' }) * const commitments = blobsToCommitments({ blobs, kzg }) * const proofs = blobsToProofs({ blobs, commitments, kzg }) + * // proofs = [[proof1], [proof2]] - one proof per blob + * ``` + * + * @example + * ```ts + * // EIP-7594 (PeerDAS) blobs + * import { + * blobsToCommitments, + * toBlobs + * } from 'viem' + * import { kzg } from './kzg' + * + * const blobs = toBlobs({ data: '0x1234' }) + * const commitments = blobsToCommitments({ blobs, kzg }) + * const proofs = blobsToProofs({ blobs, commitments, kzg, blobVersion: '7594' }) + * // proofs = [[proof1, proof2, ...proof128], [proof129, ...]] - 128 proofs per blob * ``` */ export function blobsToProofs< @@ -64,7 +88,7 @@ export function blobsToProofs< >( parameters: blobsToProofsParameters, ): blobsToProofsReturnType { - const { kzg } = parameters + const { kzg, blobVersion = '4844' } = parameters const to = parameters.to ?? (typeof parameters.blobs[0] === 'string' ? 'hex' : 'bytes') @@ -80,14 +104,33 @@ export function blobsToProofs< : parameters.commitments ) as ByteArray[] - const proofs: ByteArray[] = [] - for (let i = 0; i < blobs.length; i++) { - const blob = blobs[i] - const commitment = commitments[i] - proofs.push(Uint8Array.from(kzg.computeBlobKzgProof(blob, commitment))) + const proofs: ByteArray[][] = [] + + if (blobVersion === '7594') { + // EIP-7594: Use computeCellsAndKzgProofs and return cell proofs for each blob + if (!kzg.computeCellsAndKzgProofs) { + throw new Error( + 'KZG implementation does not support computeCellsAndKzgProofs (required for EIP-7594)', + ) + } + for (let i = 0; i < blobs.length; i++) { + const blob = blobs[i] + const [_cells, cellProofs] = kzg.computeCellsAndKzgProofs(blob) + // Each blob gets its own array of cell proofs + proofs.push(cellProofs) + } + } else { + // EIP-4844: Use computeBlobKzgProof (one proof per blob, wrapped in array) + for (let i = 0; i < blobs.length; i++) { + const blob = blobs[i] + const commitment = commitments[i] + proofs.push([Uint8Array.from(kzg.computeBlobKzgProof(blob, commitment))]) + } } return (to === 'bytes' ? proofs - : proofs.map((x) => bytesToHex(x))) as {} as blobsToProofsReturnType + : proofs.map((blobProofs) => + blobProofs.map((proof) => bytesToHex(proof)), + )) as {} as blobsToProofsReturnType } diff --git a/src/utils/blob/getBlobVersion.test.ts b/src/utils/blob/getBlobVersion.test.ts new file mode 100644 index 0000000000..62c8d074aa --- /dev/null +++ b/src/utils/blob/getBlobVersion.test.ts @@ -0,0 +1,28 @@ +import { expect, test } from 'vitest' +import { getBlobVersion } from './getBlobVersion.js' + +test('default: returns 4844', () => { + expect(getBlobVersion()).toBe('4844') +}) + +test('mainnet: returns 4844', () => { + expect(getBlobVersion({ chainId: 1 })).toBe('4844') +}) + +test('sepolia: returns 7594', () => { + expect(getBlobVersion({ chainId: 11_155_111 })).toBe('7594') +}) + +test('explicit blobVersion overrides chainId', () => { + // Override Sepolia to use 4844 + expect(getBlobVersion({ chainId: 11_155_111, blobVersion: '4844' })).toBe( + '4844', + ) + + // Override mainnet to use 7594 + expect(getBlobVersion({ chainId: 1, blobVersion: '7594' })).toBe('7594') +}) + +test('arbitrary chain: returns 4844', () => { + expect(getBlobVersion({ chainId: 12345 })).toBe('4844') +}) diff --git a/src/utils/blob/getBlobVersion.ts b/src/utils/blob/getBlobVersion.ts new file mode 100644 index 0000000000..9fefe9c902 --- /dev/null +++ b/src/utils/blob/getBlobVersion.ts @@ -0,0 +1,54 @@ +import type { ErrorType } from '../../errors/utils.js' +import type { BlobVersion } from './blobsToProofs.js' + +export type GetBlobVersionParameters = { + /** Chain ID to determine blob version for */ + chainId?: number | undefined + /** Explicit blob version override */ + blobVersion?: BlobVersion | undefined +} + +export type GetBlobVersionReturnType = BlobVersion + +export type GetBlobVersionErrorType = ErrorType + +/** + * Determines the blob version to use based on chain ID. + * + * Sepolia (chain ID 11155111) uses EIP-7594 (PeerDAS). + * All other chains default to EIP-4844. + * + * @param parameters - Chain ID and optional blob version override + * @returns The blob version to use ('4844' or '7594') + * + * @example + * ```ts + * import { getBlobVersion } from 'viem' + * + * // Sepolia uses EIP-7594 + * const version = getBlobVersion({ chainId: 11155111 }) + * // => '7594' + * + * // Other chains use EIP-4844 + * const version2 = getBlobVersion({ chainId: 1 }) + * // => '4844' + * + * // Can override with explicit version + * const version3 = getBlobVersion({ chainId: 11155111, blobVersion: '4844' }) + * // => '4844' + * ``` + */ +export function getBlobVersion( + parameters: GetBlobVersionParameters = {}, +): GetBlobVersionReturnType { + const { chainId, blobVersion } = parameters + + // If explicit version provided, use it + if (blobVersion) return blobVersion + + // Sepolia uses EIP-7594 + if (chainId === 11_155_111) return '7594' + + // Default to EIP-4844 + return '4844' +} diff --git a/src/utils/blob/toBlobSidecars.ts b/src/utils/blob/toBlobSidecars.ts index d0c7c3b39d..0e606ab94b 100644 --- a/src/utils/blob/toBlobSidecars.ts +++ b/src/utils/blob/toBlobSidecars.ts @@ -25,6 +25,8 @@ export type ToBlobSidecarsParameters< > = { /** Return type. */ to?: to | To | undefined + /** Blob version (EIP-4844 or EIP-7594). Defaults to '4844'. */ + blobVersion?: '4844' | '7594' | undefined } & OneOf< | { /** Data to transform into blobs. */ @@ -37,8 +39,8 @@ export type ToBlobSidecarsParameters< blobs: blobs | readonly Hex[] | readonly ByteArray[] /** Commitment for each blob. */ commitments: _blobsType | readonly Hex[] | readonly ByteArray[] - /** Proof for each blob. */ - proofs: _blobsType | readonly Hex[] | readonly ByteArray[] + /** Proof(s) for each blob (array of proof arrays). */ + proofs: readonly (readonly Hex[])[] | readonly (readonly ByteArray[])[] } > @@ -93,20 +95,26 @@ export function toBlobSidecars< >( parameters: ToBlobSidecarsParameters, ): ToBlobSidecarsReturnType { - const { data, kzg, to } = parameters + const { data, kzg, to, blobVersion = '4844' } = parameters const blobs = parameters.blobs ?? toBlobs({ data: data!, to }) const commitments = parameters.commitments ?? blobsToCommitments({ blobs, kzg: kzg!, to }) const proofs = - parameters.proofs ?? blobsToProofs({ blobs, commitments, kzg: kzg!, to }) + parameters.proofs ?? + blobsToProofs({ blobs, commitments, kzg: kzg!, to, blobVersion }) const sidecars: BlobSidecars = [] - for (let i = 0; i < blobs.length; i++) + for (let i = 0; i < blobs.length; i++) { + const blobProofs = proofs[i] + // For EIP-4844, blobProofs is [single_proof] + // For EIP-7594, blobProofs is [proof1, proof2, ..., proof128] + // We store the first proof in the sidecar (for EIP-4844) or all proofs (for EIP-7594) sidecars.push({ blob: blobs[i], commitment: commitments[i], - proof: proofs[i], + proof: blobProofs.length === 1 ? blobProofs[0] : [...blobProofs], }) + } return sidecars as ToBlobSidecarsReturnType } diff --git a/src/utils/kzg/defineKzg.ts b/src/utils/kzg/defineKzg.ts index 598da08cd7..3e7d111819 100644 --- a/src/utils/kzg/defineKzg.ts +++ b/src/utils/kzg/defineKzg.ts @@ -22,9 +22,11 @@ export type DefineKzgErrorType = ErrorType export function defineKzg({ blobToKzgCommitment, computeBlobKzgProof, + computeCellsAndKzgProofs, }: DefineKzgParameters): DefineKzgReturnType { return { blobToKzgCommitment, computeBlobKzgProof, - } + computeCellsAndKzgProofs, + } as DefineKzgReturnType } diff --git a/src/utils/transaction/parseTransaction.test.ts b/src/utils/transaction/parseTransaction.test.ts index 0021306ecc..ecf81f98f9 100644 --- a/src/utils/transaction/parseTransaction.test.ts +++ b/src/utils/transaction/parseTransaction.test.ts @@ -486,6 +486,85 @@ describe('eip4844', () => { }) }) +describe('eip7594', () => { + const baseEip7594 = { + ...base, + blobVersionedHashes: [ + '0x01adbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef', + ], + blobVersion: '7594', + chainId: 11_155_111, // Sepolia + } as const satisfies TransactionSerializableEIP4844 + + test('default', () => { + const serialized = serializeTransaction(baseEip7594) + const transaction = parseTransaction(serialized) + assertType(transaction) + expect(transaction).toEqual({ ...baseEip7594, type: 'eip4844' }) + }) + + test('args: fees', () => { + const args = { + ...baseEip7594, + maxFeePerBlobGas: parseGwei('2'), + maxFeePerGas: parseGwei('2'), + maxPriorityFeePerGas: parseGwei('1'), + } + const serialized = serializeTransaction(args) + const transaction = parseTransaction(serialized) + assertType(transaction) + expect(transaction).toEqual({ ...args, type: 'eip4844' }) + }) + + test('args: gas', () => { + const args = { + ...baseEip7594, + gas: 69n, + } + const serialized = serializeTransaction(args) + const transaction = parseTransaction(serialized) + assertType(transaction) + expect(transaction).toEqual({ ...args, type: 'eip4844' }) + }) + + test('args: sidecar', () => { + const args = { + ...baseEip7594, + sidecars: [ + { + blob: '0x1234', + commitment: '0x1234', + // EIP-7594: Each sidecar has an array of cell proofs + proof: ['0x1234', '0x5678'], + }, + { + blob: '0x1234', + commitment: '0x1234', + proof: ['0xabcd', '0xef01'], + }, + ], + } as const satisfies TransactionSerializableEIP4844 + const serialized = serializeTransaction(args) + const transaction = parseTransaction(serialized) + assertType(transaction) + expect(transaction).toEqual({ ...args, type: 'eip4844' }) + }) + + test('signed', async () => { + const signature = await sign({ + hash: keccak256(serializeTransaction(baseEip7594)), + privateKey: accounts[0].privateKey, + }) + const serialized = serializeTransaction(baseEip7594, signature) + const parsed = parseTransaction(serialized) + expect(parsed).toEqual({ + ...baseEip7594, + ...signature, + type: 'eip4844', + }) + }) +}) + describe('eip1559', () => { const baseEip1559 = { ...base, diff --git a/src/utils/transaction/parseTransaction.ts b/src/utils/transaction/parseTransaction.ts index 8432398d44..56c98f456c 100644 --- a/src/utils/transaction/parseTransaction.ts +++ b/src/utils/transaction/parseTransaction.ts @@ -224,14 +224,22 @@ function parseTransactionEIP4844( ): TransactionSerializableEIP4844 { const transactionOrWrapperArray = toTransactionArray(serializedTransaction) - const hasNetworkWrapper = transactionOrWrapperArray.length === 4 + // EIP-4844 has 4 elements: [tx, blobs, commitments, proofs] + // EIP-7594 has 5 elements: [tx, version, blobs, commitments, proofs] + const hasNetworkWrapper = + transactionOrWrapperArray.length === 4 || + transactionOrWrapperArray.length === 5 + const isEIP7594 = transactionOrWrapperArray.length === 5 const transactionArray = hasNetworkWrapper ? transactionOrWrapperArray[0] : transactionOrWrapperArray const wrapperArray = hasNetworkWrapper - ? transactionOrWrapperArray.slice(1) + ? isEIP7594 + ? transactionOrWrapperArray.slice(2) // Skip version byte at index 1 + : transactionOrWrapperArray.slice(1) : [] + const blobVersion = isEIP7594 ? '7594' : '4844' const [ chainId, @@ -293,12 +301,33 @@ function parseTransactionEIP4844( transaction.maxPriorityFeePerGas = hexToBigInt(maxPriorityFeePerGas) if (accessList.length !== 0 && accessList !== '0x') transaction.accessList = parseAccessList(accessList as RecursiveArray) - if (blobs && commitments && proofs) + if (blobs && commitments && proofs) { + // Group proofs by blob + const proofsArray = proofs as Hex[] + const blobsArray = blobs as Hex[] + // Calculate proofs per blob dynamically + const proofsPerBlob = Math.floor(proofsArray.length / blobsArray.length) + const groupedProofs: Hex[][] = [] + for (let i = 0; i < blobsArray.length; i++) { + const startIdx = i * proofsPerBlob + groupedProofs.push(proofsArray.slice(startIdx, startIdx + proofsPerBlob)) + } + transaction.sidecars = toBlobSidecars({ - blobs: blobs as Hex[], + blobs: blobsArray, commitments: commitments as Hex[], - proofs: proofs as Hex[], + proofs: groupedProofs, + blobVersion, }) + } + + // Set blobVersion based on wrapper detection or chain ID + if (isEIP7594) { + transaction.blobVersion = '7594' + } else if (transaction.chainId === 11_155_111) { + // Sepolia uses EIP-7594 + transaction.blobVersion = '7594' + } assertTransactionEIP4844(transaction) diff --git a/src/utils/transaction/serializeTransaction.ts b/src/utils/transaction/serializeTransaction.ts index 060eb8a6bb..dd218dfe1e 100644 --- a/src/utils/transaction/serializeTransaction.ts +++ b/src/utils/transaction/serializeTransaction.ts @@ -42,6 +42,10 @@ import { type CommitmentsToVersionedHashesErrorType, commitmentsToVersionedHashes, } from '../blob/commitmentsToVersionedHashes.js' +import { + type GetBlobVersionErrorType, + getBlobVersion, +} from '../blob/getBlobVersion.js' import { type ToBlobSidecarsErrorType, toBlobSidecars, @@ -202,6 +206,7 @@ type SerializeTransactionEIP4844ErrorType = | BlobsToCommitmentsErrorType | CommitmentsToVersionedHashesErrorType | blobsToProofsErrorType + | GetBlobVersionErrorType | ToBlobSidecarsErrorType | ConcatHexErrorType | InvalidLegacyVErrorType @@ -225,10 +230,17 @@ function serializeTransactionEIP4844( maxPriorityFeePerGas, accessList, data, + blobVersion: explicitBlobVersion, } = transaction assertTransactionEIP4844(transaction) + // Auto-detect blob version based on chain ID (Sepolia uses EIP-7594) + const blobVersion = getBlobVersion({ + chainId, + blobVersion: explicitBlobVersion, + }) + let blobVersionedHashes = transaction.blobVersionedHashes let sidecars = transaction.sidecars // If `blobs` are passed, we will need to compute the KZG commitments & proofs. @@ -253,7 +265,7 @@ function serializeTransactionEIP4844( commitments, }) if (typeof sidecars === 'undefined') { - const proofs = blobsToProofs({ blobs, commitments, kzg }) + const proofs = blobsToProofs({ blobs, commitments, kzg, blobVersion }) sidecars = toBlobSidecars({ blobs, commitments, proofs }) } } @@ -283,9 +295,27 @@ function serializeTransactionEIP4844( const { blob, commitment, proof } = sidecars[i] blobs.push(blob) commitments.push(commitment) - proofs.push(proof) + // proof can be a single proof (EIP-4844) or an array of proofs (EIP-7594) + if (Array.isArray(proof)) { + proofs.push(...proof) + } else { + proofs.push(proof) + } } + if (blobVersion === '7594') { + // EIP-7594: wrapper format with version byte + return concatHex([ + '0x03', + sidecars + ? // Wrapper format: [tx_payload_body, wrapper_version, blobs, commitments, cell_proofs] + toRlp([serializedTransaction, '0x01', blobs, commitments, proofs]) + : // Standard envelope (no wrapper version for non-sidecar transactions) + toRlp(serializedTransaction), + ]) as TransactionSerializedEIP4844 + } + + // EIP-4844: legacy format without wrapper version return concatHex([ '0x03', sidecars diff --git a/test/src/kzg.ts b/test/src/kzg.ts index 5744a6d68e..3d5688bc93 100644 --- a/test/src/kzg.ts +++ b/test/src/kzg.ts @@ -1,7 +1,8 @@ import { readFileSync } from 'node:fs' import { resolve } from 'node:path' -import { trustedSetup as fastSetup } from '@paulmillr/trusted-setups/fast.js' -import { KZG } from 'micro-eth-signer/kzg' +import { trustedSetup as fastSetup } from '@paulmillr/trusted-setups/fast-peerdas.js' + +import { KZG } from 'micro-eth-signer/advanced/kzg.js' import { bytesToHex, defineKzg, hexToBytes } from '../../src/index.js' const k = new KZG(fastSetup) @@ -17,6 +18,16 @@ export const kzg = defineKzg({ ) as `0x${string}`, ) }, + computeCellsAndKzgProofs(blob) { + const [cells, proofs] = k.computeCellsAndProofs(bytesToHex(blob)) as [ + `0x${string}`[], + `0x${string}`[], + ] + return [ + cells.map((cell) => hexToBytes(cell)), + proofs.map((proof) => hexToBytes(proof)), + ] + }, }) export const blobData = readFileSync(