UNPKG

ox

Version:

Ethereum Standard Library

498 lines 15.2 kB
import * as BlobCells from './BlobCells.js'; import * as Bytes from './Bytes.js'; import * as Errors from './Errors.js'; import * as Hash from './Hash.js'; import * as Hex from './Hex.js'; import * as Cursor from './internal/cursor.js'; import * as Kzg from './Kzg.js'; /** Blob limit per transaction. */ const blobsPerTransaction = 6; /** The number of bytes in a BLS scalar field element. */ export const bytesPerFieldElement = 32; /** The number of field elements in a blob. */ export const fieldElementsPerBlob = 4096; /** The number of bytes in a blob. */ export const bytesPerBlob = bytesPerFieldElement * fieldElementsPerBlob; /** Blob bytes limit per transaction. */ export const maxBytesPerTransaction = bytesPerBlob * blobsPerTransaction - // terminator byte (0x80). 1 - // zero byte (0x00) appended to each field element. 1 * fieldElementsPerBlob * blobsPerTransaction; /** * Transform a list of Commitments to Blob Versioned Hashes. * * @example * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const commitments = Blobs.toCommitments(blobs, { kzg }) * const versionedHashes = * Blobs.commitmentsToVersionedHashes(commitments) // [!code focus] * // @log: ['0x...', '0x...'] * ``` * * @example * ### Configuring Return Type * * It is possible to configure the return type for the Versioned Hashes with the `as` option. * * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const commitments = Blobs.toCommitments(blobs, { kzg }) * const versionedHashes = Blobs.commitmentsToVersionedHashes( * commitments, * { * as: 'Bytes' // [!code focus] * } * ) * // @log: [Uint8Array [ ... ], Uint8Array [ ... ]] * ``` * * @example * ### Versioning Hashes * * It is possible to configure the version for the Versioned Hashes with the `version` option. * * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const commitments = Blobs.toCommitments(blobs, { kzg }) * const versionedHashes = Blobs.commitmentsToVersionedHashes( * commitments, * { * version: 2 // [!code focus] * } * ) * ``` * * @param commitments - A list of commitments. * @param options - Options. * @returns A list of Blob Versioned Hashes. */ export function commitmentsToVersionedHashes(commitments, options = {}) { const { version } = options; const as = options.as ?? (typeof commitments[0] === 'string' ? 'Hex' : 'Bytes'); const hashes = []; for (const commitment of commitments) { hashes.push(commitmentToVersionedHash(commitment, { as, version, })); } return hashes; } /** * Transform a Commitment to its Blob Versioned Hash. * * @example * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const [commitment] = Blobs.toCommitments(blobs, { kzg }) * const versionedHash = * Blobs.commitmentToVersionedHash(commitment) // [!code focus] * ``` * * @example * ### Configuring Return Type * * It is possible to configure the return type for the Versioned Hash with the `as` option. * * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const [commitment] = Blobs.toCommitments(blobs, { kzg }) * const versionedHashes = Blobs.commitmentToVersionedHash( * commitment, * { * as: 'Bytes' // [!code focus] * } * ) * // @log: [Uint8Array [ ... ], Uint8Array [ ... ]] * ``` * * @example * ### Versioning Hashes * * It is possible to configure the version for the Versioned Hash with the `version` option. * * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const [commitment] = Blobs.toCommitments(blobs, { kzg }) * const versionedHashes = Blobs.commitmentToVersionedHash( * commitment, * { * version: 2 // [!code focus] * } * ) * ``` * * @param commitment - The commitment. * @param options - Options. * @returns The Blob Versioned Hash. */ export function commitmentToVersionedHash(commitment, options = {}) { const { version = 1 } = options; const as = options.as ?? (typeof commitment === 'string' ? 'Hex' : 'Bytes'); const versionedHash = Hash.sha256(commitment, { as: 'Bytes' }); versionedHash.set([version], 0); return (as === 'Bytes' ? versionedHash : Hex.fromBytes(versionedHash)); } /** * Transforms arbitrary data to {@link ox#Blobs.Blobs}. * * @example * ```ts twoslash * import { Blobs } from 'ox' * * const blobs = Blobs.from('0xdeadbeef') * ``` * * @example * ### Creating Blobs from a String * * An example of creating Blobs from a string using {@link ox#Hex.(from:function)}: * * ```ts twoslash * import { Blobs, Hex } from 'ox' * * const blobs = Blobs.from(Hex.fromString('Hello world!')) * ``` * * @example * ### Configuring Return Type * * It is possible to configure the return type for the Blobs with the `as` option. * * ```ts twoslash * import { Blobs } from 'ox' * * const blobs = Blobs.from('0xdeadbeef', { as: 'Bytes' }) * // ^? * ``` * * @param data - The data to convert to {@link ox#Blobs.Blobs}. * @param options - Options. * @returns The {@link ox#Blobs.Blobs}. */ export function from(data, options = {}) { const as = options.as ?? (typeof data === 'string' ? 'Hex' : 'Bytes'); const data_ = (typeof data === 'string' ? Bytes.fromHex(data) : data); const size_ = Bytes.size(data_); if (!size_) throw new EmptyBlobError(); if (size_ > maxBytesPerTransaction) throw new BlobSizeTooLargeError({ maxSize: maxBytesPerTransaction, size: size_, }); const blobs = []; let active = true; let position = 0; while (active) { const blob = Cursor.create(new Uint8Array(bytesPerBlob)); let size = 0; while (size < fieldElementsPerBlob) { const bytes = data_.slice(position, position + (bytesPerFieldElement - 1)); // Push a zero byte so the field element doesn't overflow the BLS modulus. blob.pushByte(0x00); // Push the current segment of data bytes. blob.pushBytes(bytes); // If we detect that the current segment of data bytes is less than 31 bytes, // we can stop processing and push a terminator byte to indicate the end of the blob. if (bytes.length < 31) { blob.pushByte(0x80); active = false; break; } size++; position += 31; } blobs.push(blob); } return (as === 'Bytes' ? blobs.map((x) => x.bytes) : blobs.map((x) => Hex.fromBytes(x.bytes))); } /** * Transforms Ox-shaped {@link ox#Blobs.Blobs} into the originating data. * * @example * ```ts twoslash * import { Blobs, Hex } from 'ox' * * const blobs = Blobs.from('0xdeadbeef') * const data = Blobs.to(blobs) // [!code focus] * // @log: '0xdeadbeef' * ``` * * @example * ### Configuring Return Type * * It is possible to configure the return type with second argument. * * ```ts twoslash * import { Blobs } from 'ox' * * const blobs = Blobs.from('0xdeadbeef') * const data = Blobs.to(blobs, 'Bytes') * // @log: Uint8Array [ 13, 174, 190, 239 ] * ``` * * @param blobs - The {@link ox#Blobs.Blobs} to transform. * @param to - The type to transform to. * @returns The originating data. */ export function to(blobs, to) { const to_ = to ?? (typeof blobs[0] === 'string' ? 'Hex' : 'Bytes'); const blobs_ = (typeof blobs[0] === 'string' ? blobs.map((x) => Bytes.fromHex(x)) : blobs); const length = blobs_.reduce((length, blob) => length + blob.length, 0); const data = Cursor.create(new Uint8Array(length)); let active = true; for (const blob of blobs_) { const cursor = Cursor.create(blob); while (active && cursor.position < blob.length) { // First byte will be a zero 0x00 byte – we can skip. cursor.incrementPosition(1); let consume = 31; if (blob.length - cursor.position < 31) consume = blob.length - cursor.position; for (let i = 0; i < consume; i++) { const byte = cursor.readByte(); const isTerminator = byte === 0x80 && !cursor.inspectBytes(cursor.remaining).includes(0x80); if (isTerminator) { active = false; break; } data.pushByte(byte); } } } const trimmedData = data.bytes.slice(0, data.position); return (to_ === 'Hex' ? Hex.fromBytes(trimmedData) : trimmedData); } /** * Transforms Ox-shaped {@link ox#Blobs.Blobs} into the originating data. * * @example * ```ts twoslash * import { Blobs, Hex } from 'ox' * * const blobs = Blobs.from('0xdeadbeef') * const data = Blobs.toHex(blobs) // [!code focus] * // @log: '0xdeadbeef' * ``` */ export function toHex(blobs) { return to(blobs, 'Hex'); } /** * Transforms Ox-shaped {@link ox#Blobs.Blobs} into the originating data. * * @example * ```ts * import { Blobs, Hex } from 'ox' * * const blobs = Blobs.from('0xdeadbeef') * const data = Blobs.toBytes(blobs) // [!code focus] * // @log: Uint8Array [ 13, 174, 190, 239 ] * ``` */ export function toBytes(blobs) { return to(blobs, 'Bytes'); } /** * Compute commitments from a list of {@link ox#Blobs.Blobs}. * * @example * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const commitments = Blobs.toCommitments(blobs, { kzg }) // [!code focus] * ``` * * @example * ### Configuring Return Type * * It is possible to configure the return type with the `as` option. * * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const commitments = Blobs.toCommitments(blobs, { * as: 'Bytes', // [!code focus] * kzg * }) * // @log: [Uint8Array [ ... ], Uint8Array [ ... ]] * ``` * * @param blobs - The {@link ox#Blobs.Blobs} to transform to commitments. * @param options - Options. * @returns The commitments. */ export function toCommitments(blobs, options) { const { kzg } = options; const as = options.as ?? (typeof blobs[0] === 'string' ? 'Hex' : 'Bytes'); const blobs_ = (typeof blobs[0] === 'string' ? blobs.map((x) => Bytes.fromHex(x)) : blobs); const commitments = []; for (const blob of blobs_) commitments.push(Uint8Array.from(kzg.blobToKzgCommitment(blob))); return (as === 'Bytes' ? commitments : commitments.map((x) => Hex.fromBytes(x))); } /** * Compute the flat list of PeerDAS (EIP-7594) cell KZG proofs for a list of * {@link ox#Blobs.Blobs}. Returns `128 * blobs.length` proofs, where * `proofs[i * 128 + j]` is the proof for cell `j` of `blobs[i]`'s extended * form. * * @example * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const cellProofs = Blobs.toCellProofs(blobs, { kzg }) // [!code focus] * ``` * * @example * ### Configuring Return Type * * It is possible to configure the return type with the `as` option. * * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const cellProofs = Blobs.toCellProofs(blobs, { * as: 'Bytes', // [!code focus] * kzg * }) * // @log: [Uint8Array [ ... ], Uint8Array [ ... ], ...] * ``` * * @param blobs - The {@link ox#Blobs.Blobs} to transform to cell proofs. * @param options - Options. * @returns The flat list of cell KZG proofs. */ export function toCellProofs(blobs, options) { const { kzg } = options; const as = options.as ?? (typeof blobs[0] === 'string' ? 'Hex' : 'Bytes'); if (as === 'Bytes') { const proofs = []; for (const blob of blobs) { const { proofs: cellProofs } = BlobCells.fromBlob(blob, { kzg, as: 'Bytes' }); for (const proof of cellProofs) proofs.push(proof); } return proofs; } const proofs = []; for (const blob of blobs) { const { proofs: cellProofs } = BlobCells.fromBlob(blob, { kzg, as: 'Hex' }); for (const proof of cellProofs) proofs.push(proof); } return proofs; } /** * Compute Blob Versioned Hashes from a list of {@link ox#Blobs.Blobs}. * * @example * ```ts twoslash * // @noErrors * import { Blobs } from 'ox' * import { kzg } from './kzg' * * const blobs = Blobs.from('0xdeadbeef') * const versionedHashes = Blobs.toVersionedHashes(blobs, { * kzg * }) // [!code focus] * ``` * * @param blobs - The {@link ox#Blobs.Blobs} to transform into Blob Versioned Hashes. * @param options - Options. * @returns The Blob Versioned Hashes. */ export function toVersionedHashes(blobs, options) { const commitments = toCommitments(blobs, options); return commitmentsToVersionedHashes(commitments, options); } /** Thrown when the blob size is too large. */ export class BlobSizeTooLargeError extends Errors.BaseError { name = 'Blobs.BlobSizeTooLargeError'; constructor({ maxSize, size }) { super('Blob size is too large.', { metaMessages: [`Max: ${maxSize} bytes`, `Given: ${size} bytes`], }); } } /** Thrown when the blob is empty. */ export class EmptyBlobError extends Errors.BaseError { name = 'Blobs.EmptyBlobError'; constructor() { super('Blob data must not be empty.'); } } /** Thrown when the blob versioned hashes are empty. */ export class EmptyBlobVersionedHashesError extends Errors.BaseError { name = 'Blobs.EmptyBlobVersionedHashesError'; constructor() { super('Blob versioned hashes must not be empty.'); } } /** Thrown when the blob versioned hash size is invalid. */ export class InvalidVersionedHashSizeError extends Errors.BaseError { name = 'Blobs.InvalidVersionedHashSizeError'; constructor({ hash, size }) { super(`Versioned hash "${hash}" size is invalid.`, { metaMessages: ['Expected: 32', `Received: ${size}`], }); } } /** Thrown when the blob versioned hash version is invalid. */ export class InvalidVersionedHashVersionError extends Errors.BaseError { name = 'Blobs.InvalidVersionedHashVersionError'; constructor({ hash, version }) { super(`Versioned hash "${hash}" version is invalid.`, { metaMessages: [ `Expected: ${Kzg.versionedHashVersion}`, `Received: ${version}`, ], }); } } //# sourceMappingURL=Blobs.js.map