npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

solshuffle

v1.0.2

Published

Lazy, efficient & stateless shuffles powered by generalised Feistel ciphers written in Solidity/Yul.

Downloads

65

Readme

🃏 solshuffle 🃏

Gas-efficient stateless shuffle implemented in Solidity/Yul, for all your onchain permutation needs.

👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇

    npm install solshuffle

👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆

1) What

You'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 <100k gas). Check out my talk at EthCC to learn how you can do extremely gas-efficient raffles with the Lazy Merkle Raffle.

Another 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.

Shoutout to @rpal_ for shilling me cool shuffle algos!

Find the accompanying TypeScript library (and reference implementation) here.

Usage

Example: Just-in-time NFT tokenId<->metadata shuffle

Difficulty level: SHADOWY SUPER CODER 🥷

Shown 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.

import { Strings } from "@openzeppelin/contracts/utils/Strings.sol";
import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import { ERC721Enumerable } from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import { FeistelShuffleOptimised } from "solshuffle/contracts/FeistelShuffleOptimised.sol";

contract ERC721Shuffled is ERC721, ERC721Enumerable {
    using Strings for uint256;

    /// @notice The first token ID. For most NFT collections, this is 1.
    uint256 public constant FIRST_TOKEN_ID = 1;
    /// @notice The max supply is used as the modulus parameter in the shuffle algos.
    uint256 public immutable maxSupply;
    /// @notice Random seed that determines the permutation of the shuffle,
    ///     should only be set once.
    bytes32 public randomSeed;

    /// @notice Return a shuffled tokenURI, calculated just-in-time when this function
    ///     is called
    /// @param tokenId token id
    /// @return URI pointing to metadata
    function tokenURI(
        uint256 tokenId
    ) public view virtual override returns (string memory) {
        require(randomSeed != 0, "random seed must be initialised!!!");
        _requireMinted(tokenId);

        // statelessly map tokenId -> shuffled tokenId,
        // deterministically according to the `randomSeed` and `rounds` parameters
        uint256 shuffledTokenId = FIRST_TOKEN_ID +
            FeistelShuffleOptimised.shuffle(
                tokenId -
                    FIRST_TOKEN_ID /** shuffle is 0-indexed, so we add offsets */,
                maxSupply /** Must stay constant */,
                uint256(randomSeed) /** Must stay constant (once set) */,
                4 /** Must stay constant */
            );

        // use the shuffled tokenId as the metadata index
        return string(abi.encodePacked(_baseURI(), shuffledTokenId.toString()));
    }
}

Specifications

The 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.

Feistel 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].

The 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.

feistel_1000_1000

Gas Benchmarks

domain = 96_722
┌─────────────────────────┬────────┬──────┬───────┬──────┐
│         (index)         │ rounds │ min  │  max  │ avg  │
├─────────────────────────┼────────┼──────┼───────┼──────┤
│ FeistelShuffleOptimised │   4    │ 4008 │ 5430  │ 4040 │
│     FeistelShuffle      │   4    │ 7255 │ 11786 │ 7297 │
└─────────────────────────┴────────┴──────┴───────┴──────┘

Security

This 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. View the audit here.

License

This library is permissively licenced with the MIT license. Send tokens to kevincharm.eth if you find the library useful for your project :^)

Disclaimer

Ensure you understand the theory behind the Generalised Feistel Cipher, such as the iteration upper bounds, which may consume more gas than the expected average in unlucky scenarios.

WEN TOKEN?

soon™

References

[1] 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.

[2] V. T. Hoang, B. Morris, and P. Rogaway, “An enciphering scheme based on a card shuffle,” in Annual Cryptology Conference, 2012, pp. 1–13.