Immutable JSON / CIP25

For the upcoming CIP25 (Enhanced Asset Information Spec) I suggest a standard for immutable content.

For files, add a sha256 field. Example;

"images": [{
        "type": "icon",
        "size": "128x128",
        "data": "",
        "sha256": "8031025a667824a188dd5479ca5cb20c5306be06ed01875f7bcc11ecb48251be"
        "type": "large",
        "data": "",
        "sha256": "2bca95eb91e6be1e9ae5b23de5f8373952987f0cdebbd7f6b0d484821951f486"

It is essential to have an onchain hash too (believe me, I found this mistake repeatedly when I ran the archiver). A practical solution would be to put a hash inside the asset description together with the json url.

I recommend a truncated sha256 hash of at least 24 hex characters or 16 base64url characters*. This hash is of the json file, meaning a token issuer who wants immutability must provide both:

  1. Hash of files inside the json file
  2. Hash of json file in asset description

Advantages of this approach

  • Easy to add manually for issuers
  • Easy to add automatically for issuers
  • Easy to verify manually
  • Easy to verify algorithmically
  • Wallets/explorers/archives can affordably keep the records they want (e.g json files and icons for all assets but ignore high resolution images)
  • No need to look up url if you have a local version
  • If url dies, the files with matching hashes are sufficient to recreate the asset (big deal for token issuers so they won’t need to trust services such as easyassets etc to remain online)
  • Truncated hash means asset url+hash should fit op_return

Examples of possible immutable asset descriptions:


The rules that would apply:

  • If protocol not specified, assume https://
  • Anything before semicolon “;” is potentially a url
  • Url must end with “.json”
  • Check for the first lowercase hex string of at least 24 char (regex: [0-9a-f]{24}) or base64url of at least 16 chars (regex: [0-9a-zA-Z-_]{16})
    • If found, verify that downloaded json matches hash
    • If url does not match, but a file exists that does match, the matching file has priority
    • If url does not match and no matching file exists either, display url content

A best practice is to keep the description at 52 char max (this is the maximum to fit op_return). For subassets the limit is less (exactly how much?) so in those cases you either need to make a shorter url, a short sub-name or just pay the extra btc fees.

*Notes on truncated hash:

  • 24 hex character or 16 base64 characters both correspond to 96 bits. Accidental collisions will never happen. An attacker who deliberately tries to make a collision will not be able to do so either. 96 bits means there are 2^96 possible values;
    = 79228162514264337593543950336
    Attacker with a billion trials/sec over 100 years, 100 * 365 * 24 * 3600 * 1e9;
    = 3153600000000000000
    Even in a hundred years, attacker has something like a one in a billion chance of making a collision. And (theoretically) if he did manage, the earliest file with a matching hash should get priority.

Notes on immutability mistakes:

  • With the archiver, I found several json files with proofs inside the json but with no onchain hash. It’s impossible to know if the json file has been tampered with, thus “proof” is worthless. This applies to easyasset links afaik. I strongly suggest they look into this.
  • MD5 hash is useless. Read this; it took 18 hours / $11.70 to make a collision.
  • Ipfs links are often “broken”. I don’t think there’s anything wrong with ipfs but the users have failed to use it correctly. Yet, these can always be recreated later as they do have a hash inside the url. Why I’m not suggesting ipfs are 1) unnecessary to rely on one specific standard, and 2) links are too long for op_return.
1 Like

Having a hash prevents the file being linked to from being modified - but what if you have a malicious json host ? it does not prevent the malicious host from changing both the the files and its hash, so to prevent that we could sign our hashes with our private key

“type”: “large”,
“data”: “”,
“sha256”: “2bca95eb91e6be1e9ae5b23de5f8373952987f0cdebbd7f6b0d484821951f486”
“sig”: “IAy9WmzdJvfqq/0IQg6baNsZqLv1IkBKo3xt97EzjNvBM1ueEM93aPk5xC62ldCE5GWE8kM+BepSWRcOCTQVSw=”

Obviously the private key that is the asset owner would need to be the one that is used

I am just throwing this idea out there

If the file and/or the sha256 property is modified by a malicious host, the onchain hash will no longer match the content.
A sig field may serve the same purpose as sha256 for immutable content at the expensive of more complicated verification. The onchain hash serves as signature, making the sig field redundant.

For a mutable asset, where content may be updated, the sig property is preferable as it proves the origin + eliminates the need for further onchain tx’s. In such cases no onchain hash should be used.


  • sha256 inside json + onchain hash is best practice for immutable asset.
  • sig inside json with no onchain hash is best practice for mutable asset.

For CIP25 I recommend both sha256 and sig as optional fields.

1 Like