Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs.wrm/links/specs.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ link-eip-4788 [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788)
link-eip-4844 [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)
link-eip-6963 [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963)
link-eip-7702 [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)
link-eip-7594 [EIP-7594](https://eips.ethereum.org/EIPS/eip-7594)

# Open Standards
link-base58 [Base58](https://en.bitcoinwiki.org/wiki/Base58)
Expand Down
9 changes: 9 additions & 0 deletions src.ts/providers/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ export interface TransactionRequest {
*/
enableCcipRead?: boolean;

/**
* The blob version (see [[link-eip-7594]]).
*/
blobVersion?: null | number;

/**
* The blob versioned hashes (see [[link-eip-4844]]).
*/
Expand Down Expand Up @@ -404,6 +409,10 @@ export function copyRequest(req: TransactionRequest): PreparedTransactionRequest
result.blobVersionedHashes = req.blobVersionedHashes.slice();
}

if ("blobVersion" in req && req.blobVersion != null) {
result.blobVersion = req.blobVersion;
}

if ("kzg" in req) { result.kzg = req.kzg; }

if ("blobs" in req && req.blobs) {
Expand Down
151 changes: 128 additions & 23 deletions src.ts/transaction/transaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ export interface TransactionLike<A = string> {
*/
accessList?: null | AccessListish;

/**
* The blob version (see [[link-eip-7594]]).
*/
blobVersion?: null | number;

/**
* The maximum fee per blob gas (see [[link-eip-4844]]).
*/
Expand Down Expand Up @@ -147,7 +152,7 @@ export interface TransactionLike<A = string> {
*/
export interface Blob {
data: string;
proof: string;
proof: string | string[];
commitment: string;
}

Expand All @@ -160,7 +165,7 @@ export interface Blob {
*/
export type BlobLike = BytesLike | {
data: BytesLike;
proof: BytesLike;
proof: BytesLike | BytesLike[];
commitment: BytesLike;
};

Expand All @@ -170,6 +175,7 @@ export type BlobLike = BytesLike | {
*/
export interface KzgLibrary {
blobToKzgCommitment: (blob: Uint8Array) => Uint8Array;
computeCellsAndKzgProofs(blob: Uint8Array): [Uint8Array[], Uint8Array[]];
computeBlobKzgProof: (blob: Uint8Array, commitment: Uint8Array) => Uint8Array;
}

Expand All @@ -186,10 +192,12 @@ export type KzgLibraryLike = KzgLibrary | {
// kzg-wasm >= 0.5.0
blobToKZGCommitment: (blob: string) => string;
computeBlobKZGProof: (blob: string, commitment: string) => string;
computeCellsAndKZGProofs: (blob: string) => { cells: string[]; proofs: string[] };
} | {
// micro-ecc-signer
blobToKzgCommitment: (blob: string) => string | Uint8Array;
computeBlobProof: (blob: string, commitment: string) => string | Uint8Array;
computeCellsAndProofs: (blob: string) => [string[], string[]];
};

function getKzgLibrary(kzg: KzgLibraryLike): KzgLibrary {
Expand Down Expand Up @@ -238,7 +246,28 @@ function getKzgLibrary(kzg: KzgLibraryLike): KzgLibrary {
assertArgument(false, "unsupported KZG library", "kzg", kzg);
};

return { blobToKzgCommitment, computeBlobKzgProof };
const computeCellsAndKzgProofs = (blob: Uint8Array): [Uint8Array[], Uint8Array[]] => {
// c-kzg >= v2.0.0
if ("computeCellsAndKzgProofs" in kzg && typeof(kzg.computeCellsAndKzgProofs) === "function") {
return kzg.computeCellsAndKzgProofs(blob);
}

// kzg-wasm >= 1.0.0
if ("computeCellsAndKZGProofs" in kzg && typeof(kzg.computeCellsAndKZGProofs) === "function") {
const result = kzg.computeCellsAndKZGProofs(hexlify(blob));
return [result.cells.map((v) => getBytes(v)), result.proofs.map((v) => getBytes(v))];
}

// micro-ecc-signer >= 0.15.0
if ("computeCellsAndProofs" in kzg && typeof(kzg.computeCellsAndProofs) === "function") {
const [cells, proofs] = kzg.computeCellsAndProofs(hexlify(blob));
return [cells.map((v) => getBytes(v)), proofs.map((v) => getBytes(v))];
}

assertArgument(false, "unsupported KZG library", "kzg", kzg);
};

return { blobToKzgCommitment, computeBlobKzgProof, computeCellsAndKzgProofs };
}

function getVersionedHash(version: number, hash: BytesLike): string {
Expand Down Expand Up @@ -562,23 +591,53 @@ function _parseEip4844(data: Uint8Array): TransactionLike {
let typeName = "3";

let blobs: null | Array<Blob> = null;
let blobVersion: null | number = null;

// Parse the network format
if (fields.length === 4 && Array.isArray(fields[0])) {
if ((fields.length === 4 || fields.length === 5) && Array.isArray(fields[0])) {
typeName = "3 (network format)";
const fBlobs = fields[1], fCommits = fields[2], fProofs = fields[3];
assertArgument(Array.isArray(fBlobs), "invalid network format: blobs not an array", "fields[1]", fBlobs);
assertArgument(Array.isArray(fCommits), "invalid network format: commitments not an array", "fields[2]", fCommits);
assertArgument(Array.isArray(fProofs), "invalid network format: proofs not an array", "fields[3]", fProofs);
assertArgument(fBlobs.length === fCommits.length, "invalid network format: blobs/commitments length mismatch", "fields", fields);
assertArgument(fBlobs.length === fProofs.length, "invalid network format: blobs/proofs length mismatch", "fields", fields);

blobVersion = fields.length === 5 ? handleNumber(fields[1], "blobVersion") : null;

const fBlobs = fields.length === 5 ? fields[2] : fields[1],
fCommits = fields.length === 5 ? fields[3] : fields[2],
fProofs = fields.length === 5 ? fields[4] : fields[3];

assertArgument(
blobVersion === null || blobVersion === 1,
"invalid network format: unsupported blobVersion",
"fields[1]",
blobVersion
);
assertArgument(
Array.isArray(fBlobs),
"invalid network format: blobs not an array",
fields.length === 5 ? "fields[2]" : "fields[1]",
fBlobs
);
assertArgument(
Array.isArray(fCommits),
"invalid network format: commitments not an array",
fields.length === 5 ? "fields[3]" : "fields[2]",
fCommits
);
assertArgument(
Array.isArray(fProofs),
"invalid network format: proofs not an array",
fields.length === 5 ? "fields[4]" : "fields[3]",
fProofs
);
assertArgument(
(blobVersion === null && fBlobs.length === fProofs.length) || (blobVersion === 1 && fBlobs.length * 128 === fProofs.length),
"invalid network format: blobs/proofs length mismatch",
"fields",
fields
);
blobs = [ ];
for (let i = 0; i < fields[1].length; i++) {
for (let i = 0; i < fBlobs.length; i++) {
blobs.push({
data: fBlobs[i],
commitment: fCommits[i],
proof: fProofs[i],
proof: blobVersion === null ? fProofs[i] : fProofs.slice(i * 128, (i + 1) * 128),
});
}

Expand All @@ -601,7 +660,8 @@ function _parseEip4844(data: Uint8Array): TransactionLike {
data: hexlify(fields[7]),
accessList: handleAccessList(fields[8], "accessList"),
maxFeePerBlobGas: handleUint(fields[9], "maxFeePerBlobGas"),
blobVersionedHashes: fields[10]
blobVersionedHashes: fields[10],
blobVersion,
};

if (blobs) { tx.blobs = blobs; }
Expand Down Expand Up @@ -647,14 +707,21 @@ function _serializeEip4844(tx: Transaction, sig: null | Signature, blobs: null |

// We have blobs; return the network wrapped format
if (blobs) {
const withBlob: Array<any> = [fields]

// Add blobVersion if it's explicitly set
if (tx.blobVersion != null && tx.blobVersion != 0) {
withBlob.push(formatNumber(tx.blobVersion, "blobVersion"));
}

withBlob.push(
blobs.map((b) => b.data),
blobs.map((b) => b.commitment),
blobs.flatMap((b) => Array.isArray(b.proof) ? b.proof : [b.proof])
);
return concat([
"0x03",
encodeRlp([
fields,
blobs.map((b) => b.data),
blobs.map((b) => b.commitment),
blobs.map((b) => b.proof),
])
encodeRlp(withBlob),
]);
}

Expand Down Expand Up @@ -741,6 +808,7 @@ export class Transaction implements TransactionLike<string> {
#chainId: bigint;
#sig: null | Signature;
#accessList: null | AccessList;
#blobVersion: null | number;
#maxFeePerBlobGas: null | bigint;
#blobVersionedHashes: null | Array<string>;
#kzg: null | KzgLibrary;
Expand Down Expand Up @@ -933,6 +1001,30 @@ export class Transaction implements TransactionLike<string> {
authorizationify(a));
}

/**
* The blob version for Cancun transactions.
*/
get blobVersion(): null | number {
const value = this.#blobVersion;
if (value == null && this.type === 3) {
return 0;
}
return value;
}
set blobVersion(value: null | number) {
if (value == null) {
this.#blobVersion = null;
return;
}
assertArgument(
Number.isInteger(value) && value >= 0 && value <= 1,
"blobVersion must be an integer between 0 and 1",
"blobVersion",
value
);
this.#blobVersion = value;
}

/**
* The max fee per blob gas for Cancun transactions.
*/
Expand Down Expand Up @@ -1025,8 +1117,14 @@ export class Transaction implements TransactionLike<string> {
}

const commit = this.#kzg.blobToKzgCommitment(data);
const proof = hexlify(this.#kzg.computeBlobKzgProof(data, commit));

let proof: string | string[];
// Encode proof based on version
if (this.#blobVersion === 1) {
const [, _proof] = this.#kzg.computeCellsAndKzgProofs(data);
proof = _proof.map((p) => hexlify(p));
} else {
proof = hexlify(this.#kzg.computeBlobKzgProof(data, commit));
}
blobs.push({
data: hexlify(data),
commitment: hexlify(commit),
Expand All @@ -1039,7 +1137,9 @@ export class Transaction implements TransactionLike<string> {
blobs.push({
data: hexlify(blob.data),
commitment: commit,
proof: hexlify(blob.proof)
proof: Array.isArray(blob.proof)
? blob.proof.map((p) => hexlify(p))
: hexlify(blob.proof),
});
versionedHashes.push(getVersionedHash(1, commit));
}
Expand Down Expand Up @@ -1074,6 +1174,7 @@ export class Transaction implements TransactionLike<string> {
this.#chainId = BN_0;
this.#sig = null;
this.#accessList = null;
this.#blobVersion = null;
this.#maxFeePerBlobGas = null;
this.#blobVersionedHashes = null;
this.#kzg = null;
Expand Down Expand Up @@ -1359,6 +1460,10 @@ export class Transaction implements TransactionLike<string> {
// This will get overwritten by blobs, if present
if (tx.blobVersionedHashes != null) { result.blobVersionedHashes = tx.blobVersionedHashes; }

if (tx.blobVersion != null) {
result.blobVersion = tx.blobVersion;
}

// Make sure we assign the kzg before assigning blobs, which
// require the library in the event raw blob data is provided.
if (tx.kzg != null) { result.kzg = tx.kzg; }
Expand Down