Home

Awesome

Noir Hydra for BN254

License: MIT

This repository contains a Noir crate implementing the Hydra stream cipher for Noir's native curve BN254.

Hydra consists of a set of multiple permutations and round functions and is optimized for encrypting large amounts of data while minimizing the number of multiplications. You can see the design of Hydra in the following picture: Hydra Design

The body of the Hydra consists of a Poseidon-like structure that expands and transforms the initial state of 4 field elements and an additional feed-forward step to 8 field elements. These eight elements serve as seeds for the keystream generation. Every head of the hydra produces eight key stream elements with the seed mentioned above and a rolling function.

Note: Every head of the Hydra uses a dedicated random round constant. To ensure soundness and to decrease necessary constraints, we pre-computed 1000 round constants with shake-128 (see Sage script section below). This means the implementation can only encrypt 8000 field elements at once. If you need to encrypt more, you have to rerun the encryption with a fresh nonce or create your own round constants.

For further information, you can read the Hydra Paper.

Performance

We tested our implementation with version 0.10.5 of the Noir compiler and the following circuit:

    use dep::hydra;

    fn main(plains: [Field; 8], key : [Field; 4], iv: [Field;4]) -> pub [Field; 8] {
        hydra::bn254::enc::encrypt(plains, key, iv)
    }

For our experiments, we varied the amount of encrypted field elements. Hydra outperforms GMiMC starting at 10 Field elements. It is possible to only use the Hydra's body to generate four key stream elements when the plaintext only consists of maximal four elements:

    use dep::hydra;

    fn main(plains: [Field; 4], key : [Field; 4], iv: [Field;4]) -> pub [Field; 4] {
        let ks = bn254::ks::hydra_body_ks(key, iv);
        let mut ciphers = [0; 4];
        for i in 0..4 {
            ciphers[i] = plains[i] + ks[i];
        }
        ciphers
    }

We also compared our implementation with the MiMC-Sponge crate. We used the MiMCSponge function to create a key stream of size N. The results can be seen in the following table (cells represent the amount of constraints by nargo info):

#Field elementsHydraHydra (only body)GMiMCMiMCSponge
461892351383723331
86621-558057319
1210702-913894182
1611142-20403132926
2015313---
2415761---

In this implementation, the bulk of constraints come from the heads of the hydra. Every head creates eight key stream elements, therefore the constraints increase on every multiple of eight. Theoretically, the heads should be much cheaper than the body, we are still investigating why the constraints increase by ~4k. Still, Hydra outperforms its peers by a good amount.

Installation

In your Nargo.toml file, add the following dependency:

[dependencies]
hydra = { tag = "v0.3.0", git = "https://github.com/TaceoLabs/noir-hydra" }

Examples

To encrypt 8 Field elements write:

    let plains = [0, 1, 2, 3, 4, 5, 6, 7];
    let key = [34245092, 1216, 54609, 32945];
    let nonce = 11561;
    let iv = [nonce, 432,324,14];
    let ciphers = hydra::bn254::enc::encrypt(plains, key, iv);
    let plains = hydra::bn254::dec::decrypt(ciphers, key, iv);

The API supports an arbitrary amount of inputs, up to 8000 field elements.

For further examples on how to use the Hydra crate, have a look in the lib.nr file in the src/ directory and check the tests.

Tools

You can find a (unoptimized) Sage implementation of hydra under tools/ as reference. We used this tool to create the round constants of the Hydra's heads.

Disclaimer

This is experimental software and is provided on an "as is" and "as available" basis. We do not give any warranties and will not be liable for any losses incurred through any use of this code base.