Experimenting with icons in OP_RETURNS

Vectoring off some thoughts @tom gave about storing metadata in op_returns, I thought it’d be interesting to explore also storing graphics in OP_RETURNS, thus eliminating the need for an external service to host an icon indefinitely.

If contracts can create tokens and issue metadata, could they also enforce that icons should be published for those tokens?

Can the rules of an NFT covenant generate graphics that are minted and included along-side the tokens?

As we know from the other forks of Bitcoin, it’s heresy to put jpegs on the blockchain, and the penalty for doing too much heresy is often death―but lets see how much trouble we can get into with an OP_RETURN less than the current limit of 223 bytes.

Background

One way to deliver fast crisp high-resolution graphics is to use line-based graphic art, or what’s called ‘vector graphics’. In comparison to jpegs or ‘raster’ images, vector graphics scale well and maintain a small data footprint regardless of displayed size.

However, the problem with vector graphic formats, is that many are propriety (or managed by a single company), and the general formats available (SVG, ps, eps) aren’t very space efficient.

But there is this linux distro, Haiku OS, it’s a community supported linux distribution for personal computing derived from BeOS. One of the interesting quirks of this linux is a custom protocol for storing vector graphics for maximum space efficiency, called “flat icon” or HVIF.

The icons are really small. Small enough to be included as file metadata. Small enough to write at length about how and why these icons are so small:

To create the images, a custom GUI application, which runs on Haiku OS is available:

There’s a general style guide for all Hakiu icons, which dictates a common isometric product design style accross all apps. The level of polish that is attainable rivals or exceeds the icons produced by some very large tech mega-corps.

There are of course a number of libraries to parse the icons outside of Hakiu OS:

  • haikon-js - A library for parsing HVIF vector icon files
  • hvif - Read and write Haiku Vector Icon Format files in Rust
  • hvif - A Haiku Vector Icon Format library for Haskell

Obviously, although the data may exist in a strange format, it’s relatively straight forward to convert to more widely supported formats later.

Let’s make some tiny vector icons.

Below is a pinup of some icons from around the space, but drawn using Icon-O-Matic in Haiku OS:

board

Note: None of the above images are the official project graphics, they’re all tiny vectors instead.

And below are the final sizes of the above icons in bytes.

Size Icon Op_Return-able
182 bytes BCH :heavy_check_mark:
330 bytes BCHF X
113 bytes BCHN :heavy_check_mark:
216 bytes BCHN_haiku :heavy_check_mark:
219 bytes Electron_Cash :heavy_check_mark:
121 bytes FBCH :heavy_check_mark:
173 bytes FURU :heavy_check_mark:
255 bytes Selene X
206 bytes WBCH :heavy_check_mark:
162 bytes ZRP :heavy_check_mark:
195 bytes ZRP_alt :heavy_check_mark:

Although there might have been some selection bias, by picking icons from the space that would also be easily drawn in vector format, most icons generally have to be fairly simple, because icons are usually small and simple by design.

Overall, many icons for major projects will probably fit in an op_return fine.

Three squares with rounded corners:

The BCHN logo is just three squares. As a flat icon, it’s one square referenced three times, with three transformations and styles.

The rounded effect was is done with a “contour” effect, which gives rounded corners.

The logo came out to 113 bytes, which would fit in an OP_RETURN.

6e 63 69 66 // ncif (`ficn` or flat icon)
03          // three styles
03 a3 1b 36 
03 f1 59 23 
03 fb b0 3a 
01          // one path
0a 
04          // four points
30 30 30 50 50 50 50 30 
03   // link the 3 shapes, some transforms and styles below ...
0a 00 01 00 12 40 c0 00 00 00 00 00 00 00 41 00 00 c5 00
00 c8 00 00 01 15 83 02 04 0a 01 01 00 12 40 10
75 00 00 00 00 00 00 40 15 f1 c5 03 a8 c0 be 2b
01 15 83 02 04 0a 02 01 00 12 3e 4e 97 00 00 00
00 00 00 3e 57 c5 c4 36 72 47 50 75 01 15 83 02
04

Old faithful:

One of the strengths of the ‘flat icon’ vector format is that circles and squares are relatively space efficient.

Custom paths take a bit more room. So in the case of outlined text, like that seen in the Bitcoin Cash logo, three custom paths make the Bitcoin Cash logo almost twice as big as the BCHN logo. The Bitcoin Cash logo below was 208 bytes, which was mostly outlining the ₿.

Maxi-minimalism:

Future Bitcoin Cash is a set of fungible tokens that are planned to exist as series with different maturation dates. Designing a generative logo template specifically for the ‘flat icon’ format and the 223 limit, there is a lot of information that can be conveyed, if custom paths are skipped and shapes are reused.

Below is a preview and set of several hundred procedurally generated icons, where each of the icons below is about 121 bytes.

To the Moon:

The slightly off-spec Selene logo below combines a number of concentric circles with a white ₿. The combination of the path for ₿ and the circles came in at 255 bytes, which will not fit in a 223 byte OP_RETURN.

Circles in HVIF format are four (4) points and eight (8) handles, or 12 points total. The below graphic has two circles referenced five times.

Note jogs at the top of outer circle are artifacts from the way haikon-js renders line end-caps, i.e. a bug in the javascript renderer.

Drawbacks

The HUGE drawback to Haiku’s icon format is that it’s not widely supported outside Haiku OS. However, it is relatively easy to render them with javascript or rust. It would also be relatively easy to have a rendering web endpoint or WebWorker.

There are also HUGE problems that come with hosting images on the internet in general. However, this protocol is targeted at 64 x 64 pixel icons, although they can easily scale for high density devices, the amount of information that can fit in a 16x16 or 64x64 grid is quite limited.

Luckily the problem of hosting images and the deliberately limited scope of HVIFs go hand in hand. Its not going to be very practical to get a lot of detailed or realistic illustrations into such a payload―especially if no accommodation is made to parse icons chained in op_returns across multiple transactions.

Conclusion

Given a live-USB and a little effort, it’s easy to make a nice vector icon. And it’d probably be fairly easy for most projects to put that asset in an op_return and be able to refer to it henceforth by outpoint.

ncif could probably be taken as a shorthand for an op_return denoting haiku vector icon format flat icons without saying much else.

It might be interesting, if there were interest, to explore the possibility of specifying regular ways of layering icons or shapes [lots of icons use the same ₿ with the same rotation], or a regular specification to embed variables in HVIF bytecode to procedurally generate NFT icons from a single vector graphic and the bytecode.parse instructions for a NFT series.

Hex Dump

Below are the above icons as hex, if anyone wants source data to play with without booting into a weird linux.

// BCH
6e63696602030ac18e05ff0402033d4943493d493d413d41424148454840
484a02033d3d443d3d3d3d363d364236463a4636463d06191211bd1211ff
03442d41323d2d3a3233363837383638373848384838493449324d3a533d
4d415344444d444d494d4e464e4b4f41483f4b404a3e4c394c3d4cbc1044
324932443202044020502030202040203020504060306050606040605060
30020a000103000a0103010002023fc1b4bcbf693cbf693fc1b4c6518c47
0451

// BCHF
6e6369660402000403750ac18ebd0a9cc1ff0ac18e05ff050104006c0902
033d4943493d493d413d41424148454840484a02033d3d443d3d3d3d363d
364236463a4636463d06191211bd1211ff03442d41323d2d3a3233363837
383638373848384838493449324d3a533d4d415344444d444d494d4e464e
4b4f41483f4b404a3e4c394c3d4cbc104432493244320a0f405c2c4c2440
20342428302438284030482850245c2860345c44544c405c0a0420202060
606060200a08203430244038502460345840404c28400a08204428404038
584060446020402840600a08284030244024502458405454404c2c540a04
284040385840404c080a000103000a0201051001178100040a030108000a
0201061001178100040a0201071001178100040a0103010002023efd0cbc
2f923c2f923efd0cbebf234861240a01020304000a020103100117810004

// BCHN
6e6369660303a31b3603f1592303fbb03a010a043030305050505030030a
0001001240c000000000000000410000c50000c8000001158302040a0101
00124010750000000000004015f1c503a8c0be2b01158302040a02010012
3e4e970000000000003e57c5c436724750750115830204

// BCHN_haiku
6e6369660603a31b3603f1592303fbb03a0433c60200120290b157400000
c0000090b1574c2000c5ffff45ffffff00000433c6060a043438344c3c54
3c3c0a063448445854505434442c343000000a0640384058485c58545050
50340a044028302c403450300a06302a40265230524c40563046060a0001
0130201c01158302040a04010420241f0a05010530242001178200040a01
0100124010750000000000004015f1c681d4c52f8a01158302040a030103
30241c01158300040a020100123e4e970000000000003e57c5c65b394750
750115830204

// Electron_Cash
6e6369660205ff03203be00602033d4943493d493d413d41424148454840
484a02033d3d443d3d3d3d363d364236463a4636463d06191211bd1211ff
03442d41323d2d3a3233363837383638373848384838493449324d3a533d
4d415344444d444d494d4e464e4b4f41483f4b404a3e4c394c3d4cbc1044
32493244320a04202c20546054602c0000020440284c2830282840283428
4c405834584c585840584c5834050a0001031001159800040a010103000a
010105000a0003010002023f2501b913973913973f250143cdc7467c330a
000105100117840004

// FBCH
6e63696605050005ff0300ff00010505ffcf03ff0000050a042424245c5c
5c5c240a0a28283428342c2c2c2c30303030342c342c3c283c0a04284028
5c345c34400a0438283838583858280a04383c385c585c583c060a010100
1001178200030a000100000a040103000a010101000a020102000a030104
00

// FURU
6e63696602020004028dffe059ffffbd5905ff02061512d11bffff034628
422e3e283a2e3634362e36343850385038533a54583e3e543e5440534048
404a4449494848494b464a40c43ac07b483d413d423e403c413abfd1bdb0
42394c39c411bd2c4e3850345037502f462e4a2e462c0204402250223022
224022302250405e305e505e5e405e505e30030a000101000a010100023f
d5493c4954bc49543fd54946a96fc63d110a010020200c

// Selene
6e636966030335c10a05ff030100000502033d4943493d493d413d414241
48454840484a02033d3d443d3d3d3d363d364236463a4636463d06191211
bd1211ff03442d41323d2d3a323336383738363837384838483849344932
4d3a533d4d415344444d444d494d4e464e4b4e41483f4b404a3e4c394c3d
4cbbf44432473244300204442cc4d62cbdce2c343c34ba9e34c1a6444cbd
ce4cc4d64c543c54c1a654ba9e020440264e26322626402632264e405a32
5a4e5a5a405a4e5a32060a010104000a0201041001178a10040a01010410
01178200040a0201031001158600040a000103000a0103020100023d9277
bb92773b92773d927747b45948759b

// WBCH
6e63696603030ac18e03ffa5bf0200040284cdfed8ff94bafe0402033d49
43493d493d413d41424148454840484a02033d3d443d3d3d3d363d364236
463a4636463d06191211bd1211ff03442d41323d2d3a3233363837383638
373848384838493449324d3a533d4d415344444d444d494d4e464e4b4e41
483f4b404a3e4c394c3d4cbc274432493244300204402250223022224022
302250405e305e505e5e405e505e30030a000103000a0103010002023fc8
a6bc98373c98373fc8a6c629834706eb0a020103100117840004

// ZRP
6e63696601033066cf040003283828382a30402632264e26583856305838
06033b2428202828283c30383c4438383802044032443c4c34503c503850
4040503844344c30443048304006033b5c5860585858445048443c484848
050a000100123ed4133ed413bed4133ed41349ff07c7511c01178523040a
00010012bed413bed4133ed413bed4134a007c4c697401178520040a0001
01000a000102000a00010300

// ZRP_alt
6e63696602033066cf05ff050003283828382a30402632264e2658385630
583806033b2428202828283c30383c4438383802044032443c4c34503c50
38504040503844344c30443048304006033b5c5860585858445048443c48
4848020440205420302020402030205040602c60546060406054602c060a
010104000a000100123ed4133ed413bed4133ed41349ff07c7511c011785
23040a00010012bed413bed4133ed413bed4134a007c4c69740117852004
0a000101000a000102000a00010300
3 Likes

LOL, you did the exact thing I suggested you do NOT do! :rofl:

One more thing.

I didn’t want to mention it above because I didn’t want anyone to beat me to it. But because the icons are so small, it’s possible to mint a set of NFTs where the NFTs are pictures.

And when you send someone else the NFT or when someone buys it, they are getting whole file with the image on the NFT.

There’s not a lot that can be done in 40 bytes, but below is how an NFT with a commitment of

6e636966010203040200010101fce3c66e010a046060602020202060010a00010000

could be rendered by a webapp or edge-worker service:

So EVERYONE will still say than an NFT is not actually the picture, that the picture doesn’t live in the NFT and doesn’t get transferred with it, but… 🤷

I’m okay with this status quo, and if it gets widespread abuse anyway we’ll think of ways to further deal with it.