{"id":17939417,"url":"https://github.com/libitx/run-hackathon-2021","last_synced_at":"2025-07-08T19:05:52.554Z","repository":{"id":119474644,"uuid":"369542406","full_name":"libitx/run-hackathon-2021","owner":"libitx","description":"Shfty Nfts - tokenise your secrets - A RUN Hackathon 2021 project","archived":false,"fork":false,"pushed_at":"2022-03-22T10:34:41.000Z","size":738,"stargazers_count":4,"open_issues_count":0,"forks_count":3,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-04T22:04:58.472Z","etag":null,"topics":["bsv","hackathon","nfts","run"],"latest_commit_sha":null,"homepage":"http://shfty.chronoslabs.net","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/libitx.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2021-05-21T13:21:17.000Z","updated_at":"2022-01-17T15:50:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"7bd5c7ae-8c32-47a9-99ee-97e853e898bc","html_url":"https://github.com/libitx/run-hackathon-2021","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/libitx/run-hackathon-2021","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libitx%2Frun-hackathon-2021","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libitx%2Frun-hackathon-2021/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libitx%2Frun-hackathon-2021/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libitx%2Frun-hackathon-2021/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/libitx","download_url":"https://codeload.github.com/libitx/run-hackathon-2021/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/libitx%2Frun-hackathon-2021/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264330513,"owners_count":23591954,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bsv","hackathon","nfts","run"],"created_at":"2024-10-29T00:07:18.775Z","updated_at":"2025-07-08T19:05:52.547Z","avatar_url":"https://github.com/libitx.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Run Hackathon 2021\n\nThis repository is the submission for the Run Hackathon 2021 from team **Shfty**.\n\n![Shfty](https://github.com/libitx/run-hackathon-2021/raw/master/media/shfty.png)\n\n#### Team:\n\n* [libitx](https://github.com/libitx)\n\n## Objectives\n\nThis Hackathon is the first time I have used RUN, so my personal goals are simply to get some experience playing with RUN and to create and share ways of integrating RUN with some of my own tools.\n\nI'm particularly keen to integrate [Univrse](https://univrse.network/) with RUN, and experiment with creating NFTs secured with encryption. I'm also keen to nail how use [paypresto](https://www.paypresto.co) as a RUN purse and release a simple plug'n'play solution as part of the [paypresto.js](https://github.com/libitx/paypresto.js) library.\n\nMy objectives:\n\n1. Create a Berry class for loading Univrse transactions into jigs ✅\n2. Create sidekick code for decoding Univrse envelopes in the RUN context ✅\n3. Create a jig class for creating NFTs that wrap Univrse data payloads directly ✅\n4. Design a paypresto Purse class that is simple to use and integrate in others' apps ✅\n5. Build a mega simple proof-of-concept app for minting and sending **Shfty Nfts** ✅\n\n## Introducing \"Shfty Nfts\"\n\n**Shfty Nfts** are secret NFTs where only the holder of the token is able to decrypt the content within. When a Shfty Nft is sent to someone else, the content is re-encrypted for the new recipient.\n\n### [🚨 👉 Check Out Shfty Nfts here 👈 🚨](https://shfty.chronoslabs.net)\n\nThe **Shfty Nfts** app is a functioning app that explores the following usecases:\n\n* Artwork NFTs where the digital artwork is both embedded inside the token and secured with signatures and encryption. When a token holder sells the artwork, they pass on not only the token, but also the access to the data within.\n* On-chain messaging/email service where each message is a token and only the holder can read the message.\n* On-chain file-sharing service with the same properties.\n\nAs this is a hackathon app, the following caveats apply:\n\n* The app relies on RUNs public cache APIs which are development tools. If things break, this is probably why. A production app would need its own cache.\n* Currently can only send tokens to other Shfty users. This is just for pragmatism, in fact existing Paymail capabilities would allow broader adoption of Shfty Nfts if other wallets chose to support it.\n\nThe Shfty Nfts stack consists of:\n\n* Minimal Phoenix back end app\n* SQLite database - again for pragmatism - any database can be used in production.\n* Alpine.js for light-weight sprinklings of JavaScript goodies\n\nThe app source code can be found at the path `app`. A few of the highlights include...\n\n### 1. Novel pure Bitcoin authentication mechanism\n\nThis isn't really RUN specific but I think it's cool and worth mentioning. When signing in a wallet is deterministically generated client-side using a `pbkdf` process on the username and password. To authenticate the client signs a masked CSRF token and sends the signature, token and xpub to the server. If the token and the signature is valid, the app lets the user in. **No passwords ever go over the wire**.\n\nSome code worth checking out:\n\n* Client-side [Wallet class](https://github.com/libitx/run-hackathon-2021/blob/master/app/assets/js/util/wallet.js) and [Sign in component](https://github.com/libitx/run-hackathon-2021/blob/master/app/assets/js/components/sign-in.js)\n* Server-side [Auth module](https://github.com/libitx/run-hackathon-2021/blob/master/app/lib/shfty/auth/auth.ex), [Auth plug](https://github.com/libitx/run-hackathon-2021/blob/master/app/lib/shfty/auth/plug.ex) and [Auth controller](https://github.com/libitx/run-hackathon-2021/blob/master/app/lib/shfty_web/controllers/auth_controller.ex)\n\n### 2. Shfty Nfty minter\n\nUsers can mint their own **Shfty Nft** jigs. A Univrse envelope is created containing any data payload, either text or binary data. I have limited the payload to 300kb out of courtesy to RUN's public APIs but in theory BSV happily supports 10 MB transactions today. The envelope is signed by the minter so future recipients can always verify their token against the original minters signature. Finally the payload is encrypted and wrapped in an NFT jig.\n\nKey code to check out:\n\n* [The minter page JavaScript component](https://github.com/libitx/run-hackathon-2021/blob/master/app/assets/js/components/minter.js)\n* [The minter page HTML view](https://github.com/libitx/run-hackathon-2021/blob/master/app/lib/shfty_web/live/components/minter.html.leex)\n\n### 3. Send functionality\n\nUsers can send their **Shfty Nft** jigs to other users. This is where things get a little funky. Ideally the re-encryption should happen inside the RUN context but that's not currently possible so we have to pull the Univrse Envelope out of the jig, decrypt it, re-encrypt it for the new recipient, update the jig and send it on.\n\nCurrently this functionality is limited to sending to other Shfty users. A websocket API is used to query users by username and returns their owner address and identity pubkey. However this functionality is standard Paymail stuff so there is no reason why Shfty Nfts can't be sent to any paymail user.\n\nThe code worth checking out:\n\n* [The token page JavaScript component](https://github.com/libitx/run-hackathon-2021/blob/master/app/assets/js/components/token.js) (check out the `send()` function)\n* [The token page HTML view](https://github.com/libitx/run-hackathon-2021/blob/master/app/lib/shfty_web/live/components/token.html.leex)\n* [Server-side socket API for searching users](https://github.com/libitx/run-hackathon-2021/blob/master/app/lib/shfty_web/live/wallet_live.ex)\n\n### 4. Custom Paypresto purse\n\nCredit where it's due, [Joshua already cracked this](https://mint.tique.run/dist/classes.js). I wanted to generalise this solution so it can be packaged up in the [paypresto.js](https://github.com/libitx/paypresto.js) library.\n\nMy solution involves provding a `beforePay()` callback (used to mount the widget in the HTML doc), and a `afterPay()` callback to handle any after effects once the RUN transaction is broadcast.\n\n* [Check out the Paypresto Purse class code](https://github.com/libitx/run-hackathon-2021/blob/master/app/assets/js/util/presto-purse.js)\n\nThe usage API looks like this:\n\n```javascript\nimport { PrestoPurse, embed } from 'paypresto.js'\n\nconst purse = new PrestoPurse({\n  key: wallet.purse.privKey,\n  description: 'Shfty shenanigans',\n  beforePay: payment =\u003e {\n    payment.mount(embed('#paypresto'))\n  },\n  afterPay: _tx =\u003e {\n    location.replace('/thanks')\n  }\n})\n\nconst run = new Run({\n  purse,\n  ...\n})\n```\n\nI think that's nice and simple. I plan to release this as part of paypresto soon - once I've sat on the API design for a few days and satisfied myself it is right. 👍\n\n## RUN specific code\n\nIn creating **Shfty Nfts**, I have added a handful of code creations to the RUN network.\n\n### 1. `U` Berry class\n\nThe `U` class loads external [Univrse](https://univrse.network/) envelope transactions into a JavaScript object for use in your jigs.\n\n| code | network | source         | location                                                                       |\n| ---- | ------- | -------------- | ------------------------------------------------------------------------------ |\n| `U`  | `main`  | [code](u-code) | [`73c0da3d071389ec188ab9160ede4d8e929ce14ed793c117e17512276eca076d_o1`](u-loc) |\n\n[u-code]: \u003chttps://github.com/libitx/run-hackathon-2021/blob/master/run/extras/u.js\u003e\n[u-loc]: \u003chttps://run.network/explorer/?query=73c0da3d071389ec188ab9160ede4d8e929ce14ed793c117e17512276eca076d_o1\u0026network=main\u003e\n\nExample:\n\n```javascript\nconst U = await run.load('73c0da3d071389ec188ab9160ede4d8e929ce14ed793c117e17512276eca076d_o1')\n\n// Load Univrse with full outpoint address\nconst env = U.load('ae0d9a823a577978bd2e92658590cf1877aad8cadd11224112b660a8cfb6b3f0.out.0')\n// Or load the first found Univrse object from a txid\nconst env = U.load('ae0d9a823a577978bd2e92658590cf1877aad8cadd11224112b660a8cfb6b3f0')\n\n// API\nenv.header        // =\u003e envelope header object\nenv.payload       // =\u003e envelope payload\nenv.signature     // =\u003e if present, signature object or array of signatures\nenv.recipient     // =\u003e if present, recipient object or array of recipients\nenv.$rawHex       // =\u003e the raw envelope is also stored as a hex string\n```\n\n### 2. Sidekick code for decoding Univrse envelopes\n\nThe `Envelope` class is used to decode raw Univrse binary data into a structured Envelope object. The `CBOR` class decodes Concise Binary Object Representation data into JavaScript values.\n\nThis is a minimal implementation of Univrse and the Envelope class does not provide the following:\n\n* **Crypto** - implementing all the crypto algorithms in pure JavaScript and without the convenience of web crypto sounds like a fun task if you're a complete psychopath. Plus the limitations of the RUN environment - no big numbers, no random numbers - makes me question whether its even feasible?\n* **Encoding** - I omitted CBOR and Envelope encoding for brevity. Because we can't sign or encrypt within RUN, it feels low priority. For now, if you want to manipulate the Univrse envelope, it must be pulled outside of RUN and put back in as a new binary value.\n\n\n| code       | network | source            | location                                                                          |\n| ---------- | ------- | ----------------- | --------------------------------------------------------------------------------- |\n| `Envelope` | `main`  | [code](env-code)  | [`73c0da3d071389ec188ab9160ede4d8e929ce14ed793c117e17512276eca076d_o2`](enc-loc)  |\n| `CBOR`     | `main`  | [code](cbor-code) | [`73c0da3d071389ec188ab9160ede4d8e929ce14ed793c117e17512276eca076d_o3`](cbor-loc) |\n\n[env-code]: \u003chttps://github.com/libitx/run-hackathon-2021/blob/master/run/extras/envelope.js\u003e\n[env-loc]: \u003chttps://run.network/explorer/?query=73c0da3d071389ec188ab9160ede4d8e929ce14ed793c117e17512276eca076d_o2\u0026network=main\u003e\n[cbor-code]: \u003chttps://github.com/libitx/run-hackathon-2021/blob/master/run/extras/cbor.js\u003e\n[cbor-loc]: \u003chttps://run.network/explorer/?query=73c0da3d071389ec188ab9160ede4d8e929ce14ed793c117e17512276eca076d_o3\u0026network=main\u003e\n\nCredit where it's due. The original authors of the CBOR code:\n\n* [cbor-redux](https://github.com/aaronhuggins/cbor-redux) - Copyright (c) 2020-2021 Aaron Huggins - MIT License\n* [cbor-js](https://github.com/paroga/cbor-js) - Copyright (c) 2014-2016 Patrick Gansterer - MIT License\n\n### 3. Jig classes for Univrse envelopes\n\nIntroducing **Shfty Nfts** and **Limited Shfty Nfts**. Two basic Jig classes that can be used to tokenise Univrse envelopes. The limited class acts as a base class which can be extended from to create limited run NFTs.\n\n| code              | network | source                | location                                                                               |\n| ----------------- | ------- | --------------------- | -------------------------------------------------------------------------------------- |\n| `ShftyNft`        | `main`  | [code](shfty-code)    | [`48df6857b6fc86d112e558302575a46f88cfff37f58fb9ddc1f5f514a065db1c_o1`](shifty-loc)    |\n| `LimitedShftyNft` | `main`  | [code](lm-shfty-code) | [`48df6857b6fc86d112e558302575a46f88cfff37f58fb9ddc1f5f514a065db1c_o2`](lm-shifty-loc) |\n\n[shfty-code]: \u003chttps://github.com/libitx/run-hackathon-2021/blob/master/run/jigs/shfty-nft.js\u003e\n[shfty-loc]: \u003chttps://run.network/explorer/?query=48df6857b6fc86d112e558302575a46f88cfff37f58fb9ddc1f5f514a065db1c_o1\u0026network=main\u003e\n[lm-shfty-code]: \u003chttps://github.com/libitx/run-hackathon-2021/blob/master/run/jigs/limited-shfty-nft.js\u003e\n[lm-shfty-loc]: \u003chttps://run.network/explorer/?query=48df6857b6fc86d112e558302575a46f88cfff37f58fb9ddc1f5f514a065db1c_o2\u0026network=main\u003e\n\n**ShftyNft** Examples:\n\n```javascript\nimport { Envelope } from 'univrse'\n\nconst ShftyNft = await run.load('48df6857b6fc86d112e558302575a46f88cfff37f58fb9ddc1f5f514a065db1c_o1')\n\n// Creating a ShftyNft token from a U berry\nconst env1 = U.load('ae0d9a823a577978bd2e92658590cf1877aad8cadd11224112b660a8cfb6b3f0.out.0')\nconst token1 = new ShftyNft(env1)\n\n// API (same as U example above)\ntoken1.env.raw     // =\u003e raw envelope Uint8Array\ntoken1.env.header  // =\u003e envelope header object\n// etc...\n\n// Create a ShftyNft token from an encrypted Envelope\nconst env2 = Envelope.wrap('This is a token', { proto: 'foo.bar' })\nawait env2.encrypt(ownerKey, { alg: 'ECDH-ES+A128GCM' })\nconst buf2 = env2.toBuffer()\nconst token2 = new ShftyNft( new Uint8Array(buf2.buffer, buf2.byteOffset, buf2.byteLength) )\n\n// Sending the token to a new recipient\nconst buf3 = Buffer.from(token2.env.$rawHex, 'hex')\nconst env3 = Envelope.fromBuffer(buf3)\nawait env3.decrypt(ownerKey)\nawait env3.encrypt(recipientKey, { alg: 'ECDH-ES+A128GCM' })\nconst buf4 = env3.toBuffer()\ntoken2.send(recipientAddr, new Uint8Array(buf4.buffer, buf4.byteOffset, buf4.byteLength))\n```\n\n**LimitedShftyNft** Examples:\n\n```javascript\n// Creating a limited run Shfty Nft token\nconst LimitedShftyNft = await run.load('48df6857b6fc86d112e558302575a46f88cfff37f58fb9ddc1f5f514a065db1c_o2')\nclass MyNFT extends LimitedShftyNft {}\nMyNFT.total = 100\n\n// Token must be created with `mint()` function\nconst token = MyNFT.mint(env)\n// error will be thrown if `mint()` called more than 100 times\n\n// API\ntoken.num         // =\u003e 1\ntoken.env         // =\u003e as above\n```\n\n---\n\n## Thank you\n\nThank you for taking the time to check out [Shfty Nfts](https://shfty.chronoslabs.net) and this README. 🙏\n\nCopyright © 2021 libitx","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flibitx%2Frun-hackathon-2021","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flibitx%2Frun-hackathon-2021","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flibitx%2Frun-hackathon-2021/lists"}