{"id":18929765,"url":"https://github.com/kevincharm/solshuffle","last_synced_at":"2025-08-30T14:16:39.006Z","repository":{"id":246727552,"uuid":"539550980","full_name":"kevincharm/solshuffle","owner":"kevincharm","description":"Gas-efficient stateless shuffle on Ethereum","archived":false,"fork":false,"pushed_at":"2024-06-30T15:31:28.000Z","size":65,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-08-21T02:53:12.292Z","etag":null,"topics":["ethereum","evm","feistel","permutation","shuffle","smart-contracts","solidity","yul"],"latest_commit_sha":null,"homepage":"","language":"Solidity","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kevincharm.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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}},"created_at":"2022-09-21T15:12:00.000Z","updated_at":"2025-03-25T01:23:15.000Z","dependencies_parsed_at":null,"dependency_job_id":"72060270-2a20-484c-8e97-2a528b769459","html_url":"https://github.com/kevincharm/solshuffle","commit_stats":null,"previous_names":["kevincharm/solshuffle"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/kevincharm/solshuffle","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevincharm%2Fsolshuffle","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevincharm%2Fsolshuffle/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevincharm%2Fsolshuffle/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevincharm%2Fsolshuffle/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kevincharm","download_url":"https://codeload.github.com/kevincharm/solshuffle/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevincharm%2Fsolshuffle/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272858568,"owners_count":25005100,"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","status":"online","status_checked_at":"2025-08-30T02:00:09.474Z","response_time":77,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["ethereum","evm","feistel","permutation","shuffle","smart-contracts","solidity","yul"],"created_at":"2024-11-08T11:35:03.411Z","updated_at":"2025-08-30T14:16:38.947Z","avatar_url":"https://github.com/kevincharm.png","language":"Solidity","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🃏 solshuffle 🃏\n\nGas-efficient stateless shuffle implemented in Solidity/Yul, for all your onchain permutation needs.\n\n👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇\n\n```sh\n    npm install solshuffle\n```\n\n👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆\n\n## 1) What\n\nYou've probably tried writing a raffle in Solidity. How much does it cost to pick 10 winners? 100? 1000? Probably millions of gas. Using `solshuffle`, you can determine the draw sequence of the user at the time of claiming. Combine this with a Merkle tree and you can have extremely efficient raffles (think cutting 10M gas down to \u003c100k gas). Check out [my talk at EthCC](https://www.youtube.com/watch?v=d7C1pLKM_Oc) to learn how you can do extremely gas-efficient raffles with the [Lazy Merkle Raffle](https://docs.fairy.dev/theory/lazy-merkle-raffle).\n\nAnother application for `solshuffle` is to shuffle NFT token identifiers. You've probably seen NFT contracts that simply add a randomised offset and call that a \"shuffle\". Now you can stop faking it and actually shuffle your token identifiers.\n\nShoutout to [@rpal\\_](https://twitter.com/rpal_) for shilling me cool shuffle algos!\n\nFind the accompanying TypeScript library (and reference implementation) [here](https://github.com/kevincharm/gfc-fpe).\n\n## Usage\n\n### Example: Just-in-time NFT tokenId\u003c-\u003emetadata shuffle\n\n\u003e Difficulty level: SHADOWY SUPER CODER 🥷\n\nShown below is a truncated example of how you'd shuffle your ERC721 metadata using the `FeistelShuffleOptimised` library, after calling a VRF (or whatever) to set a `randomSeed`.\n\n```solidity\nimport { Strings } from \"@openzeppelin/contracts/utils/Strings.sol\";\nimport { ERC721 } from \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";\nimport { ERC721Enumerable } from \"@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol\";\nimport { FeistelShuffleOptimised } from \"solshuffle/contracts/FeistelShuffleOptimised.sol\";\n\ncontract ERC721Shuffled is ERC721, ERC721Enumerable {\n    using Strings for uint256;\n\n    /// @notice The first token ID. For most NFT collections, this is 1.\n    uint256 public constant FIRST_TOKEN_ID = 1;\n    /// @notice The max supply is used as the modulus parameter in the shuffle algos.\n    uint256 public immutable maxSupply;\n    /// @notice Random seed that determines the permutation of the shuffle,\n    ///     should only be set once.\n    bytes32 public randomSeed;\n\n    /// @notice Return a shuffled tokenURI, calculated just-in-time when this function\n    ///     is called\n    /// @param tokenId token id\n    /// @return URI pointing to metadata\n    function tokenURI(\n        uint256 tokenId\n    ) public view virtual override returns (string memory) {\n        require(randomSeed != 0, \"random seed must be initialised!!!\");\n        _requireMinted(tokenId);\n\n        // statelessly map tokenId -\u003e shuffled tokenId,\n        // deterministically according to the `randomSeed` and `rounds` parameters\n        uint256 shuffledTokenId = FIRST_TOKEN_ID +\n            FeistelShuffleOptimised.shuffle(\n                tokenId -\n                    FIRST_TOKEN_ID /** shuffle is 0-indexed, so we add offsets */,\n                maxSupply /** Must stay constant */,\n                uint256(randomSeed) /** Must stay constant (once set) */,\n                4 /** Must stay constant */\n            );\n\n        // use the shuffled tokenId as the metadata index\n        return string(abi.encodePacked(_baseURI(), shuffledTokenId.toString()));\n    }\n}\n```\n\n## Specifications\n\nThe stateless shuffle implemented by `solshuffle` is the Generalised Feistel Cipher, but we'll just call it the Feistel Shuffle. The Feistel shuffle is cheap, coming in at ~4350 gas to calculate a permutation for a single index for a list size of 10,000.\n\nFeistel networks are based on _round functions_, and these are run a fixed number of times, as specified in the `rounds` parameter. As long as you input a cryptographically secure random `seed`, it is sufficient to set `rounds = 4` to make a _strong_ pseudorandom permutation [[1]](#m-luby-and-c-rackoff-1988).\n\nThe figure below shows the distribution of shuffled indices (y-axis) against their original indices (x-axis) when picking $y \\mid 0 \\leq y \\lt 1000$ with `modulus = 10_000`. Each colour represents a different run, with its own 32-byte cryptorandom seed. Every run sets `rounds = 4`. Re-run this for yourself with `yarn plot:feistel`.\n\n![feistel_1000_1000](https://user-images.githubusercontent.com/10385659/193012477-60f74cef-c7eb-4a91-ad93-30ee6c7ab4c6.png)\n\n### Gas Benchmarks\n\n```\ndomain = 96_722\n┌─────────────────────────┬────────┬──────┬───────┬──────┐\n│         (index)         │ rounds │ min  │  max  │ avg  │\n├─────────────────────────┼────────┼──────┼───────┼──────┤\n│ FeistelShuffleOptimised │   4    │ 4008 │ 5430  │ 4040 │\n│     FeistelShuffle      │   4    │ 7255 │ 11786 │ 7297 │\n└─────────────────────────┴────────┴──────┴───────┴──────┘\n```\n\n## Security\n\nThis repository has not received an individual security audit. However, both `FeistelShuffle.sol` and `FeistelShuffleOptimised.sol` were audited by Trail of Bits as part of the Ethereum Foundation's [Devcon Auction-Raffle contracts](https://github.com/efdevcon/devcon-raffle). [View the audit here](https://github.com/efdevcon/devcon-raffle/blob/849ad0b18e48a10900c37a5275e5b16b997abf59/audits/Ethereum%20Foundation%20Devcon%20Auction-Raffle%20Summary%20Report.pdf).\n\n## License\n\nThis library is permissively licenced with the MIT license. Send tokens to `kevincharm.eth` if you find the library useful for your project :^)\n\n## Disclaimer\n\nEnsure you understand the theory behind the [Generalised Feistel Cipher](https://github.com/kevincharm/gfc-fpe/blob/master/README.md), such as the iteration upper bounds, which may consume more gas than the expected average in unlucky scenarios.\n\n## WEN TOKEN?\n\nsoon™\n\n## References\n\n\u003ca name=\"m-luby-and-c-rackoff-1988\"\u003e[1]\u003c/a\u003e M. Luby and C. Rackoff, “How to construct pseudorandom permutations from pseudorandom functions,” SIAM Journal on Computing, vol. 17, no. 2, pp. 373–386, 1988.\n\n\u003ca name=\"v-t-hoang-2012\"\u003e[2]\u003c/a\u003e V. T. Hoang, B. Morris, and P. Rogaway, “An enciphering scheme based on a card shuffle,” in Annual Cryptology Conference, 2012, pp. 1–13.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkevincharm%2Fsolshuffle","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkevincharm%2Fsolshuffle","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkevincharm%2Fsolshuffle/lists"}