ESIP-8: Ethscription Attachments aka "BlobScriptions"
Last updated
Last updated
The introduction of blobs in EIP-4844 enables anyone to store data on Ethereum for 10x to 100x cheaper than calldata. This comes at a cost, however: the Ethereum protocol doesn't guarantee the availability of blob data for more than 18 days.
However, on a practical level it is not clear how burdensome this limitation will be. Because L2s use blobs to store transaction data there will be strong incentives to create publicly accessible archives of blob data to enhance the transparency and auditability of Layer 2s.
Also, like IPFS, blob data is completely decentralized—as long as one person has blob data it can be verified and used by anyone.
This ESIP proposes using blobs to store data within the Ethscriptions Protocol. We presuppose the ready availability of blob data and require indexers to store or find user blob data along with the other blockchain data the Ethscriptions Protocol currently uses.
Specifically, ESIP-8 proposes a new "sidecar" attachment field for Ethscriptions that is composed from the data in one or more blobs. This field is in addition to the existing content field.
The name "Ethscription Attachment" is preferred over "Ethscription Blob" (or similar) because transactions can have multiple blobs, but ethscriptions can only have one attachment (that is composed of all the blobs together).
Consider the ethscription created by this Sepolia transaction. The transaction's calldata contains the hex data 0x646174613a2c68656c6c6f2066726f6d20457468736372697074696f6e2063616c6c6461746121
which corresponds to the dataURI "data:,hello from Ethscription calldata!" which becomes the ethscription's content.
The transaction's blobs, when interpreted according to the rules described below, contains the data for this image which becomes the ethscription's attachment:
All new ethscriptions have an optional attachment
field. If an ethscription is created in a transaction with no blobs this field will be null
.
If an ethscription's creation transaction does include blobs and the ethscription was created via calldata (i.e., not via an event emission), its blobs are concatenated and interpreted as an untagged CBOR object (as defined by RFC 8949) that decodes into a hash with exactly these keys:
content
contentType
If the concatenated data is a valid CBOR object, and that object decodes into a hash with exactly those two fields, an attachment for the ethscription is created.
The case in which the blobs are invalid and an attachment is not created is handled identically to the case in which there are no blobs at all. I.e., the ethscription is still created if it's otherwise valid, just with no attachment.
Note:
There is no uniqueness requirement for the attachment's content and/or contentType.
Attachment content
, contentType
, and the container CBOR object itself can each be optionally gzipped with a maximum compression ratio of 10x.
The attachment is not valid if:
If the CBOR object has a tag
If the decoded object his not a hash
If the decoded hash's keys aren't exactly content
and contentType
. There cannot be extra keys.
The values of content
and contentType
aren't both strings (either binary or UTF-8).
When such an attachment exists, the indexer's API must include the path for retrieving it in an attachment_path
field in the JSON representation of an ethscription with at most a one block delay between ethscription creation and inclusion of the URL. For example, if an ethscription is created in block 15, the attachment_url must appear no later than block 17.
The attachment_url field will be available in addition to the content_uri
field.
contentType
Max LengthTo enable performant filtering by contentType
, indexers must only store the first 1,000 characters of the user-submitted content type. Content types of more than 1,000 characters will be truncated, but the attachment will still be valid.
You can use the cbor
package and Viem's toBlobs
:
Blob data is available on a block-level through the blob_sidecars
API endpoint available on Ethereum Beacon nodes. If you don't want to run a node yourself, you can use Quicknode.
The input to this function is a "block id," which is a slot number (not block number) or block root. Block roots are available on normal Ethereum API requests, but only for the previous block (the field is parentBeaconBlockRoot
).
This means that attachments must be populated on a one block delay.
Because the Beacon API only provides blob information on a block level, it requires some additional logic to match blobs to the transactions that created them. Fortunately, transactions now have a blobVersionedHashes
field that can be computed from the kzg_commitments
field on the block-level blob data.
Here's an example implementation (it's O(n^2)
but the numbers involved are small)
At a high-level we use normalize the blob data, concatenate it, and CBOR-decode it. However blobs have a few interesting quirks that make this more challenging:
Currently blobs have a minimum length of 128kb. If your data is smaller than that you'll have to pad it (probably will null bytes) to the full length.
Blobs are composed of "segments" of 32 bytes, none of which, when interpreted as an integer, can exceed the value of the cryptography-related "BLS modulus", which is 52435875175126190479447740508185965837690552500527637822603658699938581184513.
So if you want to use blobs you need a protocol for communicating where the data ends and a mechanism for ensuring no 32 byte segment is too large.
Here Ethscriptions will follow Viem's approach:
Left-pad each segment with a null byte. A 0x00
in the most significant byte ensures no segment can be larger than the BLS modulus.
End the content of every blob with 0x80
, which, when combined with the rule above, provides an unambiguous way to determine the length of the data in the blob.
When a blob creator follows these rules (or just use's Viem's toBlobs
), you can decode it into bytes by using Viem's fromBlobs
. There is a Ruby implementation as well in the appendix.
Once you decode the blob you can create an attachment using something like this class.
It's useful to be able to generate a unique hash of an Ethscription Attachment in order for indexers to avoid storing duplicate data and for users to determine which other ethscriptions have the same attachment. This can be done in many ways, but to promote uniformity ESIP-8 defines this canonical method of hashing Ethscription Attachments:
Compute the sha256 hash of the attachment's ungzipped contentType
and content
fields.
Remove the leading 0x
if present.
Concatenate the hex string representations of the hashes with the contentType
hash first.
Hash this concatenated string and add a 0x
prefix.
Here is a Javascript implementation:
And a Ruby implementation:
BlobUtils