ox
Version:
Ethereum Standard Library
181 lines • 6.45 kB
JavaScript
import * as Blobs from './Blobs.js';
import * as Bytes from './Bytes.js';
import * as Errors from './Errors.js';
import * as Hex from './Hex.js';
/** The number of field elements in a cell. */
export const fieldElementsPerCell = 64;
/** The number of bytes in a cell (64 × 32). */
export const bytesPerCell = 2048;
/** The number of cells per extended blob. */
export const cellsPerExtBlob = 128;
/**
* Compute the cells and KZG proofs for a single blob (PeerDAS, EIP-7594).
*
* Returns 128 cells (each 2048 bytes) and 128 cell-level KZG proofs (each 48 bytes).
*
* @example
* ```ts twoslash
* // @noErrors
* import { BlobCells, Blobs } from 'ox'
* import { kzg } from './kzg'
*
* const [blob] = Blobs.from('0xdeadbeef')
* const { cells, proofs } = BlobCells.fromBlob(blob, { kzg })
* ```
*
* @param blob - The blob to convert.
* @param options - Options.
* @returns The cells and proofs.
*/
export function fromBlob(blob, options) {
const { kzg } = options;
const as = options.as ?? (typeof blob === 'string' ? 'Hex' : 'Bytes');
const blob_ = (typeof blob === 'string' ? Bytes.fromHex(blob) : blob);
const { cells, proofs } = kzg.computeCellsAndKzgProofs(blob_);
if (as === 'Bytes')
return {
cells: cells.map((c) => Uint8Array.from(c)),
proofs: proofs.map((p) => Uint8Array.from(p)),
};
return {
cells: cells.map((c) => Hex.fromBytes(c)),
proofs: proofs.map((p) => Hex.fromBytes(p)),
};
}
/**
* Verify a batch of cell KZG proofs against their commitments (PeerDAS, EIP-7594).
*
* Each cell at index `cellIndices[i]` is checked against the commitment
* `commitments[i]` using the proof `proofs[i]`.
*
* @example
* ```ts twoslash
* // @noErrors
* import { BlobCells, Blobs } from 'ox'
* import { kzg } from './kzg'
*
* const [blob] = Blobs.from('0xdeadbeef')
* const [commitment] = Blobs.toCommitments([blob], { kzg })
* const { cells, proofs } = BlobCells.fromBlob(blob, { kzg })
* const valid = BlobCells.verify({
* cells,
* cellIndices: cells.map((_, i) => i),
* commitments: cells.map(() => commitment),
* proofs,
* kzg
* })
* ```
*
* @param options - Verification options.
* @returns Whether all (commitment, cell, proof) tuples verify.
*/
export function verify(options) {
const { kzg, commitments, cellIndices, cells, proofs } = options;
const toBytes = (x) => typeof x === 'string' ? Bytes.fromHex(x) : x;
return kzg.verifyCellKzgProofBatch(commitments.map(toBytes), [...cellIndices], cells.map(toBytes), proofs.map(toBytes));
}
/**
* Reconstruct all 128 cells (and their KZG proofs) of an extended blob from
* at least 64 known cells (PeerDAS, EIP-7594).
*
* @example
* ```ts twoslash
* // @noErrors
* import { BlobCells } from 'ox'
* import { kzg } from './kzg'
*
* // Reconstruct from 64 of 128 cells.
* const { cells, proofs } = BlobCells.recover(
* knownIndices, // e.g. [0, 2, 4, …]
* knownCells,
* { kzg }
* )
* ```
*
* @param cellIndices - The indices of the known cells (must contain ≥ 64 distinct values).
* @param cells - The known cells, parallel to `cellIndices`.
* @param options - Options.
* @returns The full set of 128 cells and 128 proofs.
*/
export function recover(cellIndices, cells, options) {
const { kzg } = options;
if (cellIndices.length !== cells.length)
throw new MismatchedLengthsError({
label: 'cellIndices/cells',
a: cellIndices.length,
b: cells.length,
});
if (cells.length < cellsPerExtBlob / 2)
throw new InsufficientCellsError({ count: cells.length });
const as = options.as ?? (typeof cells[0] === 'string' ? 'Hex' : 'Bytes');
const cells_ = cells.map((c) => typeof c === 'string' ? Bytes.fromHex(c) : c);
const { cells: recoveredCells, proofs } = kzg.recoverCellsAndKzgProofs([...cellIndices], cells_);
if (as === 'Bytes')
return {
cells: recoveredCells.map((c) => Uint8Array.from(c)),
proofs: proofs.map((p) => Uint8Array.from(p)),
};
return {
cells: recoveredCells.map((c) => Hex.fromBytes(c)),
proofs: proofs.map((p) => Hex.fromBytes(p)),
};
}
/**
* Build the 128 PeerDAS data columns from a list of blobs (EIP-7594).
*
* For each column index `i ∈ [0, 128)`, produces a {@link ox#BlobCells.DataColumn}
* containing one cell + one cell proof per blob (at column `i` of each blob's
* extended form), alongside the blob-level commitments needed to verify them.
*
* @example
* ```ts twoslash
* // @noErrors
* import { BlobCells, Blobs } from 'ox'
* import { kzg } from './kzg'
*
* const blobs = Blobs.from('0xdeadbeef')
* const columns = BlobCells.toDataColumns(blobs, { kzg }) // 128 columns
* ```
*
* @param blobs - The blobs to convert.
* @param options - Options.
* @returns 128 data columns.
*/
export function toDataColumns(blobs, options) {
const { kzg } = options;
const as = options.as ?? (typeof blobs[0] === 'string' ? 'Hex' : 'Bytes');
const commitments = Blobs.toCommitments(blobs, { kzg, as: 'Bytes' });
const perBlob = blobs.map((b) => fromBlob(b, { kzg, as: 'Bytes' }));
const columns = [];
for (let i = 0; i < cellsPerExtBlob; i++) {
columns.push({
index: i,
cells: perBlob.map((b) => b.cells[i]),
proofs: perBlob.map((b) => b.proofs[i]),
commitments,
});
}
if (as === 'Bytes')
return columns;
return columns.map((c) => ({
index: c.index,
cells: c.cells.map((x) => Hex.fromBytes(x)),
proofs: c.proofs.map((x) => Hex.fromBytes(x)),
commitments: c.commitments.map((x) => Hex.fromBytes(x)),
}));
}
/** Thrown when fewer than {@link ox#BlobCells.cellsPerExtBlob}/2 cells are passed to {@link ox#BlobCells.recover}. */
export class InsufficientCellsError extends Errors.BaseError {
name = 'BlobCells.InsufficientCellsError';
constructor({ count }) {
super(`\`BlobCells.recover\` requires at least ${cellsPerExtBlob / 2} cells; received ${count}.`);
}
}
/** Thrown when two parallel input arrays have different lengths. */
export class MismatchedLengthsError extends Errors.BaseError {
name = 'BlobCells.MismatchedLengthsError';
constructor({ label, a, b }) {
super(`Length mismatch for ${label}: ${a} vs ${b}.`);
}
}
//# sourceMappingURL=BlobCells.js.map