Awesome
Noir Hydra for BN254
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:
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 elements | Hydra | Hydra (only body) | GMiMC | MiMCSponge |
---|---|---|---|---|
4 | 6189 | 2351 | 3837 | 23331 |
8 | 6621 | - | 5580 | 57319 |
12 | 10702 | - | 9138 | 94182 |
16 | 11142 | - | 20403 | 132926 |
20 | 15313 | - | - | - |
24 | 15761 | - | - | - |
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.