Ethereal content
One of the recurring problems with NFT digital art has been the volatility of storage. While the NFT recording ownership of the artwork lives on a blockchain such as Ethereum, the content itself—the actual image or video—is usually too large to keep on chain. Instead there is a URL reference in the NFT pointing to the content. In the early days those were garden-variety web links. That made all kinds of shenanigans possible, some intended others not:
- Since websites can go away for good (because the domain is not renewed) the NFT could disappear for good.
- Alternatively the website could still be around but its contents can change. There is no rule that says some link such as https://example.com/MyNFT will always return the same content. The buyer of an NFT could find that the artwork they purchased has morphed. It could even be different based on time of day or the person accessing the link. (This last example was demonstrated in a recent stunt arguing that Web3 is not decentralized at all, by returning deliberately different image when the NFT is accessed through OpenSea.)
IPFS, Arweave and similar systems have been proposed as a solution to this problem. Instead of uploading NFTs to a website which may go out of business or start returing bogus concept, they are instead stored on special distributed systems. In this blog post we will describe a proof-of-concept for approximating the same effect using vanilla HTTPS links.
Before diving into the implementation details, we need to distinguish between two different requirements behind the ambiguous goal of “persistence:”
1. Immutability
2. Censorship resistance
The first one states that the content does not change over time. If the image looked a certain way when you purchased the NFT, it will always look that way when you return to view it again. (Unless of course the NFT itself incorporates elements of randomness, such as an image rendered slightly different each time. But even in that scenario, the algorithmic model for generating the image itself is constant.)
The second property states that the content is always accessible. If you were able to view the NFT once, you can do so again in the future. It will not disappear or become unavailable due to a system outage.
This distinction is important because each can be achieved independently of the other. Immutability alone may be sufficient for some use cases. In fact there is an argument to be made that #2 is not a desirable requirement in the absolute sense. Most would agree that beheading videos, CSAM or even copyrighted content should be taken down even if they were minted as an NFT.
To that end we focus on the first objective only: create an NFT that is immutable. There is no assurance that the NFT will be accessible at all times, or that it cannot be permanently taken down if enough people agree. But we can guarantee that if you can view the NFT, it will always be this particular image or that particular movie.
Subresource Integrity
At first it looks like there is already a web-standard that solves this problem out of the box: subresource integrity or SRI for short. With SRI one can link to content such as a Javascript library or a stylesheet hosted by an untrusted third-party. If that third-party attempts to tamper with the appearance and functionality of your website by serving an altered version of the content—for example a back-doored version of the Javascript library that logs keystrokes and steals passwords—it will be detected and blocked from loading. Note that SRI does not guarantee availability: that website may still have an outage or it may outright refuse to serve any content. Both of those events will still interfere with the functioning of the page; but at least the originating site can detect this condition and display an error. From a security perspective that is a major improvement over continuing to execute logic that has been corrupted (undetected) by a third-party.
Limitations & caveats
While the solution sketched here is based on SRI, there are two problems that preclude a straightforward application:
- SRI only works inside HTML documents.
- SRI only applies to link and script elements. Strictly speaking this is not a limitation of the specification, but the practical reality of the extent most web-browsers have implemented the spec.
To make the first limitation more concrete, this is how a website would include a snippet of JS hosted by a third-party:
<script src="https://example.com/third-party-library.js"
integrity="sha256-xzKeRPLnOjN6inNfYWKfDt4RIa7mMhQhOlafengSDvU=" crossorigin="anonymous">
That second attribute is SRI at work. By specifying the expected SHA256 hash of the Javascript code to be included in this page, we are preventing the third-party from serving any other code. Even the slightest alteration to the script returned will be flagged as an error and prevent the code from executing.
It is tempting to conclude that this one trick is sufficient to create an immutable NFT (according to the modest definition above) but there are two problems.
1. There is no “short-hand” version of SRI that encodes this integrity check in the URL itself. In an ideal world one could craft a third-party link along the lines of:
https://example.com/code.js#/script[@integrity=‘sha256-xzKeRPLnOjN6inNfYWKfDt4RIa7mMhQhOlafengSDvU=']”
This (entirely hypothetical) version is borrowing syntax from XPath, combining URIs with an XML-style query language to “search” for an element that meets a particular criteria, in this case having a given SHA256 hash. But as of this writing, there is no web standard for incorporating integrity checks into the URI this way. (The closest is an RFC for hash-links.) For now we have to content ourselves with specifying the integrity as an out-of-band HTML attribute of the element.
2. As a matter of browser implementations, SRI is only applied to specific types of content; notably, javascript and stylesheets. This is consistent across Chrome, Firefox and Edge. Neither images or iframes are covered. That means even if we could somehow solve the first problem, we can not link to an “immutable” image by using an ordinary HTML image tag.
Emulating SRI for images
Working around both of these limitations requires a more complicated solution, where the document is built up in stages. While it is not possible to make a plain HTTPS URL immutable due to limitation #1 in SIR, there is one scheme that supports immutability by default. In fact all URLs of this type are always immutable. This is the “data” scheme where the content is inlined; it is in the URL itself. Since no content is retrieved from an external server, this is immutable by definition. Data URLs can encode an HTML document, which serves as our starting point or stage #1. The URL associated with the NFT on-chain will have this form.
In theory we could encode an entire HTML document, complete with embedded images, this way. But that runs into a more mundane problem: blockchain space is expensive and the NFT URL lives on chain. That calls for minimizing the amount of data stored within the smart-contract, using only the minimal amount of HTML to boostrap the intended content. In our case, the specific HTML document will follow a simple template:
<!DOCTYPE html>
<html>
<head>
<script src="https://example.com/stage2.js"
integrity="sha256-xzKeRPLnOjN6inNfYWKfDt4RIa7mMhQhOlafengSDvU=" crossorigin="anonymous"> </script></head></html>
This is just a way of invoking stage #2, which is a chunk of bootstrap JavaScript hosted on an external service and made immutable using SRI. If that hosting service decides to go rogue and start returning different content, the load will fail, leaving the user starting at a blank page. But the hosting service cannot successfully cause altered javascript to execute, because of the integrity check enforced by SRI.
Stage #2 itself is also simple. It is a way of invoking stage #3, where the actual content rendering occurs.
var contents='” … contents of stage #3 HTML document … “;document.write(contents);
This replaces the current document by new HTML from the string. The heavy lifting takes place after the third stage has loaded:
- It will fetch additional javascript libraries, using SRI to guarantee that they cannot be tampered with.
- In particular, we pull in an existing open-source library from 2017 to emulate SRI for images, since the NFT is an image. This polyfill library supports an alternative syntax for loading images, with the URL and expected SHA256 hash specified as proprietary HTML attributes.
- Stage #3 also contains a reference to the actual NFT image. But this image is not loaded using the standard <img src=”…”> syntax in HTML; that would not be covered by SRI due to the problem of browser support discussed above.
- Instead, we wait until the document has rendered and kick-off custom script that invokes the JS library to do a controlled image load, comparing the content retrieved by XmlHttpRequest against the integrity check to make sure the server returned our expected NFT.
- If the server returned the correct image, it will be rendered. Otherwise a brusque modal dialog appears to inform the viewer that something is wrong.
Putting it all together, here is a data URL encoding an immutable NFT:
data:text/html;charset=utf-8;base64,PCFET0NUWVBFIGh0bWw+CjxodG1sPgogIDxoZWFkPgogICAgPHNjcmlwdCBzcmM9Imh0dHBzOi8vd3d3LmlkZWVzZml4ZXMuaW8vaW1tdXRhYmxlL3N0YWdlMi5qcyIKCSAgICBpbnRlZ3JpdHk9InNoYTI1Ni1YSlF3UkFvZWtUa083eE85Y3ozaExrZFBDSzRxckJINDF5dlNSaXg4MmhVPSIKCSAgICBjcm9zc29yaWdpbj0iYW5vbnltb3VzIj4KICAgIDwvc2NyaXB0PgogIDwvaGVhZD4KPC9odG1sPgo=
We can also embed it on other webpages (such as NFT marketplaces and galleries) using an iframe. as in this example:
Chrome does not allow navigating the top-level document to a data URL, requiring indirection through the iframe. In this case the viewer itself must be trusted, since it can cheat by pointing the iframe at a bogus URL instead of the correct scheme printed above. But such corruptions are only “local” since other honest viewers will continue to enforce the integrity check.
What happens if the server hosting the image were to replace our hypothetical motorcycle NFT by a different picture?
Linking to the image with a plain HTTPS URL will display the corrupted NFT:

But going through the immutable URL above will detect the tampering attempt and not render the image:

CP
You must be logged in to post a comment.