UNPKG

ox

Version:

Ethereum Standard Library

277 lines (258 loc) 7.8 kB
import { BlobCells, Blobs, Bytes, Hex } from 'ox' import { describe, expect, test } from 'vp/test' import { blobData, kzg } from '../../../test/kzg.js' // KZG cell-proof computations are CPU-heavy and can exceed the default // 20s testTimeout on busy CI runners; give every test that touches the // KZG backend (fromBlob / verify / recover / toDataColumns) 60s headroom. const timeout = 60_000 const [blobHex] = Blobs.from(Hex.fromString(blobData)) const [blobBytes] = Blobs.from(Bytes.fromString(blobData)) describe('fromBlob', () => { test( 'default (Hex in → Hex out)', () => { const { cells, proofs } = BlobCells.fromBlob(blobHex!, { kzg }) expect(cells.length).toBe(128) expect(proofs.length).toBe(128) for (const cell of cells) { expect(typeof cell).toBe('string') expect(Hex.size(cell as Hex.Hex)).toBe(2048) } for (const proof of proofs) { expect(typeof proof).toBe('string') expect(Hex.size(proof as Hex.Hex)).toBe(48) } }, timeout, ) test( 'Bytes in → Bytes out', () => { const { cells, proofs } = BlobCells.fromBlob(blobBytes!, { kzg }) expect(cells.length).toBe(128) expect(proofs.length).toBe(128) for (const cell of cells) { expect(cell).toBeInstanceOf(Uint8Array) expect((cell as Bytes.Bytes).length).toBe(2048) } for (const proof of proofs) { expect(proof).toBeInstanceOf(Uint8Array) expect((proof as Bytes.Bytes).length).toBe(48) } }, timeout, ) test( 'as: "Bytes" overrides Hex input', () => { const { cells, proofs } = BlobCells.fromBlob(blobHex!, { kzg, as: 'Bytes', }) expect(cells[0]).toBeInstanceOf(Uint8Array) expect(proofs[0]).toBeInstanceOf(Uint8Array) }, timeout, ) test( 'as: "Hex" overrides Bytes input', () => { const { cells, proofs } = BlobCells.fromBlob(blobBytes!, { kzg, as: 'Hex', }) expect(typeof cells[0]).toBe('string') expect(typeof proofs[0]).toBe('string') }, timeout, ) test( 'Hex and Bytes outputs are equivalent', () => { const fromHex = BlobCells.fromBlob(blobHex!, { kzg }) const fromBytes = BlobCells.fromBlob(blobBytes!, { kzg }) for (let i = 0; i < fromHex.cells.length; i++) { expect(Hex.toBytes(fromHex.cells[i] as Hex.Hex)).toEqual( fromBytes.cells[i], ) expect(Hex.toBytes(fromHex.proofs[i] as Hex.Hex)).toEqual( fromBytes.proofs[i], ) } }, timeout, ) }) describe('verify', () => { test( 'returns true for a well-formed batch', () => { const [commitment] = Blobs.toCommitments([blobBytes!], { kzg }) const { cells, proofs } = BlobCells.fromBlob(blobBytes!, { kzg }) expect( BlobCells.verify({ kzg, commitments: cells.map(() => commitment!), cellIndices: cells.map((_, i) => i), cells, proofs, }), ).toBe(true) }, timeout, ) test( 'returns false when a cell is tampered', () => { const [commitment] = Blobs.toCommitments([blobBytes!], { kzg }) const { cells, proofs } = BlobCells.fromBlob(blobBytes!, { kzg }) const tampered = cells.map((c, i) => { if (i !== 3) return c const copy = Uint8Array.from(c as Bytes.Bytes) copy[0] = copy[0]! ^ 0x01 return copy }) expect( BlobCells.verify({ kzg, commitments: cells.map(() => commitment!), cellIndices: cells.map((_, i) => i), cells: tampered, proofs, }), ).toBe(false) }, timeout, ) test( 'accepts Hex cells/proofs/commitments', () => { const [commitment] = Blobs.toCommitments([blobBytes!], { kzg, as: 'Hex' }) const { cells, proofs } = BlobCells.fromBlob(blobHex!, { kzg }) expect( BlobCells.verify({ kzg, commitments: cells.map(() => commitment!), cellIndices: cells.map((_, i) => i), cells, proofs, }), ).toBe(true) }, timeout, ) }) describe('recover', () => { test( 'reconstructs the full set from 64 cells (Bytes)', () => { const { cells, proofs } = BlobCells.fromBlob(blobBytes!, { kzg }) const keptIndices: number[] = [] const keptCells: Bytes.Bytes[] = [] for (let i = 0; i < cells.length; i += 2) { keptIndices.push(i) keptCells.push(cells[i] as Bytes.Bytes) } const recovered = BlobCells.recover(keptIndices, keptCells, { kzg }) expect(recovered.cells.length).toBe(128) expect(recovered.proofs.length).toBe(128) for (let i = 0; i < cells.length; i++) { expect(recovered.cells[i]).toEqual(cells[i]) expect(recovered.proofs[i]).toEqual(proofs[i]) } }, timeout, ) test( 'reconstructs the full set from 64 cells (Hex)', () => { const { cells } = BlobCells.fromBlob(blobHex!, { kzg }) const keptIndices: number[] = [] const keptCells: Hex.Hex[] = [] for (let i = 0; i < cells.length; i += 2) { keptIndices.push(i) keptCells.push(cells[i] as Hex.Hex) } const recovered = BlobCells.recover(keptIndices, keptCells, { kzg }) expect(typeof recovered.cells[0]).toBe('string') expect(typeof recovered.proofs[0]).toBe('string') for (let i = 0; i < cells.length; i++) expect(recovered.cells[i]).toEqual(cells[i]) }, timeout, ) test('throws when fewer than 64 cells are provided', () => { expect(() => BlobCells.recover( [0, 1, 2], [new Uint8Array(2048), new Uint8Array(2048), new Uint8Array(2048)], { kzg }, ), ).toThrow(BlobCells.InsufficientCellsError) }) test('throws when cellIndices and cells lengths differ', () => { expect(() => BlobCells.recover([0, 1], [new Uint8Array(2048)], { kzg }), ).toThrow(BlobCells.MismatchedLengthsError) }) }) describe('toDataColumns', () => { test( 'returns 128 columns, one per column index', () => { const blobs = Blobs.from(Hex.fromString(blobData)) const columns = BlobCells.toDataColumns(blobs, { kzg }) expect(columns.length).toBe(128) columns.forEach((col, i) => { expect(col.index).toBe(i) expect(col.cells.length).toBe(blobs.length) expect(col.proofs.length).toBe(blobs.length) expect(col.commitments.length).toBe(blobs.length) }) }, timeout, ) test( 'each column verifies via BlobCells.verify', () => { const blobs = Blobs.from(Bytes.fromString(blobData)).slice(0, 1) const columns = BlobCells.toDataColumns(blobs, { kzg }) // Sample one column for the verify smoke test (full sweep ~30s). const col = columns[7]! expect( BlobCells.verify({ kzg, commitments: col.commitments, cellIndices: col.cells.map(() => col.index), cells: col.cells, proofs: col.proofs, }), ).toBe(true) }, timeout, ) test( 'respects `as: "Bytes"`', () => { const blobs = Blobs.from(Hex.fromString(blobData)) const columns = BlobCells.toDataColumns(blobs, { kzg, as: 'Bytes' }) expect(columns[0]!.cells[0]).toBeInstanceOf(Uint8Array) }, timeout, ) }) test('exports', () => { expect(Object.keys(BlobCells)).toMatchInlineSnapshot(` [ "fieldElementsPerCell", "bytesPerCell", "cellsPerExtBlob", "fromBlob", "verify", "recover", "toDataColumns", "InsufficientCellsError", "MismatchedLengthsError", ] `) })