Skip to content

Commit 1fb56f7

Browse files
committed
feat: support EIP-7594
1 parent 9fd9d41 commit 1fb56f7

File tree

3 files changed

+118
-23
lines changed

3 files changed

+118
-23
lines changed

docs.wrm/links/specs.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ link-eip-4788 [EIP-4788](https://eips.ethereum.org/EIPS/eip-4788)
3636
link-eip-4844 [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844)
3737
link-eip-6963 [EIP-6963](https://eips.ethereum.org/EIPS/eip-6963)
3838
link-eip-7702 [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702)
39+
link-eip-7594 [EIP-7594](https://eips.ethereum.org/EIPS/eip-7594)
3940

4041
# Open Standards
4142
link-base58 [Base58](https://en.bitcoinwiki.org/wiki/Base58)

src.ts/providers/provider.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,10 @@ export function copyRequest(req: TransactionRequest): PreparedTransactionRequest
404404
result.blobVersionedHashes = req.blobVersionedHashes.slice();
405405
}
406406

407+
if ("blobVersion" in req && req.blobVersion != null) {
408+
result.blobVersion = req.blobVersion;
409+
}
410+
407411
if ("kzg" in req) { result.kzg = req.kzg; }
408412

409413
if ("blobs" in req && req.blobs) {

src.ts/transaction/transaction.ts

Lines changed: 113 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ export interface TransactionLike<A = string> {
109109
*/
110110
accessList?: null | AccessListish;
111111

112+
/**
113+
* The blob version (see [[link-eip-7594]]).
114+
*/
115+
blobVersion?: null | number;
116+
112117
/**
113118
* The maximum fee per blob gas (see [[link-eip-4844]]).
114119
*/
@@ -147,7 +152,7 @@ export interface TransactionLike<A = string> {
147152
*/
148153
export interface Blob {
149154
data: string;
150-
proof: string;
155+
proof: string | string[];
151156
commitment: string;
152157
}
153158

@@ -160,7 +165,7 @@ export interface Blob {
160165
*/
161166
export type BlobLike = BytesLike | {
162167
data: BytesLike;
163-
proof: BytesLike;
168+
proof: BytesLike | BytesLike[];
164169
commitment: BytesLike;
165170
};
166171

@@ -170,6 +175,7 @@ export type BlobLike = BytesLike | {
170175
*/
171176
export interface KzgLibrary {
172177
blobToKzgCommitment: (blob: Uint8Array) => Uint8Array;
178+
computeCellsAndKzgProofs(blob: Uint8Array): [Uint8Array[], Uint8Array[]];
173179
computeBlobKzgProof: (blob: Uint8Array, commitment: Uint8Array) => Uint8Array;
174180
}
175181

@@ -238,7 +244,15 @@ function getKzgLibrary(kzg: KzgLibraryLike): KzgLibrary {
238244
assertArgument(false, "unsupported KZG library", "kzg", kzg);
239245
};
240246

241-
return { blobToKzgCommitment, computeBlobKzgProof };
247+
const computeCellsAndKzgProofs = (blob: Uint8Array): [Uint8Array[], Uint8Array[]] => {
248+
if ("computeCellsAndKzgProofs" in kzg && typeof kzg.computeCellsAndKzgProofs === "function") {
249+
return kzg.computeCellsAndKzgProofs(blob);
250+
}
251+
252+
assertArgument(false, "unsupported KZG library", "kzg", kzg);
253+
};
254+
255+
return { blobToKzgCommitment, computeBlobKzgProof, computeCellsAndKzgProofs };
242256
}
243257

244258
function getVersionedHash(version: number, hash: BytesLike): string {
@@ -562,23 +576,53 @@ function _parseEip4844(data: Uint8Array): TransactionLike {
562576
let typeName = "3";
563577

564578
let blobs: null | Array<Blob> = null;
579+
let blobVersion: null | number = null;
565580

566581
// Parse the network format
567-
if (fields.length === 4 && Array.isArray(fields[0])) {
582+
if ((fields.length === 4 || fields.length === 5) && Array.isArray(fields[0])) {
568583
typeName = "3 (network format)";
569-
const fBlobs = fields[1], fCommits = fields[2], fProofs = fields[3];
570-
assertArgument(Array.isArray(fBlobs), "invalid network format: blobs not an array", "fields[1]", fBlobs);
571-
assertArgument(Array.isArray(fCommits), "invalid network format: commitments not an array", "fields[2]", fCommits);
572-
assertArgument(Array.isArray(fProofs), "invalid network format: proofs not an array", "fields[3]", fProofs);
573-
assertArgument(fBlobs.length === fCommits.length, "invalid network format: blobs/commitments length mismatch", "fields", fields);
574-
assertArgument(fBlobs.length === fProofs.length, "invalid network format: blobs/proofs length mismatch", "fields", fields);
575-
584+
blobVersion = fields.length === 5 ? handleNumber(fields[1], "blobVersion") : null;
585+
586+
const fBlobs = fields.length === 5 ? fields[2] : fields[1],
587+
fCommits = fields.length === 5 ? fields[3] : fields[2],
588+
fProofs = fields.length === 5 ? fields[4] : fields[3];
589+
590+
assertArgument(
591+
blobVersion === null || blobVersion === 1,
592+
"invalid network format: unsupported blobVersion",
593+
"fields[1]",
594+
blobVersion
595+
);
596+
assertArgument(
597+
Array.isArray(fBlobs),
598+
"invalid network format: blobs not an array",
599+
fields.length === 5 ? "fields[2]" : "fields[1]",
600+
fBlobs
601+
);
602+
assertArgument(
603+
Array.isArray(fCommits),
604+
"invalid network format: commitments not an array",
605+
fields.length === 5 ? "fields[3]" : "fields[2]",
606+
fCommits
607+
);
608+
assertArgument(
609+
Array.isArray(fProofs),
610+
"invalid network format: proofs not an array",
611+
fields.length === 5 ? "fields[4]" : "fields[3]",
612+
fProofs
613+
);
614+
assertArgument(
615+
(blobVersion === null && fBlobs.length === fProofs.length) || (blobVersion === 1 && fBlobs.length * 128 === fProofs.length),
616+
"invalid network format: blobs/proofs length mismatch",
617+
"fields",
618+
fields
619+
);
576620
blobs = [ ];
577-
for (let i = 0; i < fields[1].length; i++) {
621+
for (let i = 0; i < fBlobs.length; i++) {
578622
blobs.push({
579623
data: fBlobs[i],
580624
commitment: fCommits[i],
581-
proof: fProofs[i],
625+
proof: blobVersion === null ? fProofs[i] : fProofs.slice(i * 128, (i + 1) * 128),
582626
});
583627
}
584628

@@ -601,7 +645,8 @@ function _parseEip4844(data: Uint8Array): TransactionLike {
601645
data: hexlify(fields[7]),
602646
accessList: handleAccessList(fields[8], "accessList"),
603647
maxFeePerBlobGas: handleUint(fields[9], "maxFeePerBlobGas"),
604-
blobVersionedHashes: fields[10]
648+
blobVersionedHashes: fields[10],
649+
blobVersion,
605650
};
606651

607652
if (blobs) { tx.blobs = blobs; }
@@ -647,14 +692,21 @@ function _serializeEip4844(tx: Transaction, sig: null | Signature, blobs: null |
647692

648693
// We have blobs; return the network wrapped format
649694
if (blobs) {
695+
const withBlob: Array<any> = [fields]
696+
697+
// Add blobVersion if it's explicitly set
698+
if (tx.blobVersion != null && tx.blobVersion != 0) {
699+
withBlob.push(formatNumber(tx.blobVersion, "blobVersion"));
700+
}
701+
702+
withBlob.push(
703+
blobs.map((b) => b.data),
704+
blobs.map((b) => b.commitment),
705+
blobs.flatMap((b) => Array.isArray(b.proof) ? b.proof : [b.proof])
706+
);
650707
return concat([
651708
"0x03",
652-
encodeRlp([
653-
fields,
654-
blobs.map((b) => b.data),
655-
blobs.map((b) => b.commitment),
656-
blobs.map((b) => b.proof),
657-
])
709+
encodeRlp(withBlob),
658710
]);
659711
}
660712

@@ -741,6 +793,7 @@ export class Transaction implements TransactionLike<string> {
741793
#chainId: bigint;
742794
#sig: null | Signature;
743795
#accessList: null | AccessList;
796+
#blobVersion: null | number;
744797
#maxFeePerBlobGas: null | bigint;
745798
#blobVersionedHashes: null | Array<string>;
746799
#kzg: null | KzgLibrary;
@@ -933,6 +986,30 @@ export class Transaction implements TransactionLike<string> {
933986
authorizationify(a));
934987
}
935988

989+
/**
990+
* The blob version for Cancun transactions.
991+
*/
992+
get blobVersion(): null | number {
993+
const value = this.#blobVersion;
994+
if (value == null && this.type === 3) {
995+
return 0;
996+
}
997+
return value;
998+
}
999+
set blobVersion(value: null | number) {
1000+
if (value == null) {
1001+
this.#blobVersion = null;
1002+
return;
1003+
}
1004+
assertArgument(
1005+
Number.isInteger(value) && value >= 0 && value <= 1,
1006+
"blobVersion must be an integer between 0 and 1",
1007+
"blobVersion",
1008+
value
1009+
);
1010+
this.#blobVersion = value;
1011+
}
1012+
9361013
/**
9371014
* The max fee per blob gas for Cancun transactions.
9381015
*/
@@ -1025,8 +1102,14 @@ export class Transaction implements TransactionLike<string> {
10251102
}
10261103

10271104
const commit = this.#kzg.blobToKzgCommitment(data);
1028-
const proof = hexlify(this.#kzg.computeBlobKzgProof(data, commit));
1029-
1105+
let proof: string | string[];
1106+
// Encode proof based on version
1107+
if (this.#blobVersion === 1) {
1108+
const [, _proof] = this.#kzg.computeCellsAndKzgProofs(data);
1109+
proof = _proof.map((p) => hexlify(p));
1110+
} else {
1111+
proof = hexlify(this.#kzg.computeBlobKzgProof(data, commit));
1112+
}
10301113
blobs.push({
10311114
data: hexlify(data),
10321115
commitment: hexlify(commit),
@@ -1039,7 +1122,9 @@ export class Transaction implements TransactionLike<string> {
10391122
blobs.push({
10401123
data: hexlify(blob.data),
10411124
commitment: commit,
1042-
proof: hexlify(blob.proof)
1125+
proof: Array.isArray(blob.proof)
1126+
? blob.proof.map((p) => hexlify(p))
1127+
: hexlify(blob.proof),
10431128
});
10441129
versionedHashes.push(getVersionedHash(1, commit));
10451130
}
@@ -1074,6 +1159,7 @@ export class Transaction implements TransactionLike<string> {
10741159
this.#chainId = BN_0;
10751160
this.#sig = null;
10761161
this.#accessList = null;
1162+
this.#blobVersion = null;
10771163
this.#maxFeePerBlobGas = null;
10781164
this.#blobVersionedHashes = null;
10791165
this.#kzg = null;
@@ -1359,6 +1445,10 @@ export class Transaction implements TransactionLike<string> {
13591445
// This will get overwritten by blobs, if present
13601446
if (tx.blobVersionedHashes != null) { result.blobVersionedHashes = tx.blobVersionedHashes; }
13611447

1448+
if (tx.blobVersion != null) {
1449+
result.blobVersion = tx.blobVersion;
1450+
}
1451+
13621452
// Make sure we assign the kzg before assigning blobs, which
13631453
// require the library in the event raw blob data is provided.
13641454
if (tx.kzg != null) { result.kzg = tx.kzg; }

0 commit comments

Comments
 (0)