Skip to content

Commit a8fa05d

Browse files
Feat/roman/hash docs (#556)
## Describe the changes This PR... ## Linked Issues Resolves # --------- Co-authored-by: Jeremy Felder <[email protected]>
1 parent ea71faf commit a8fa05d

File tree

10 files changed

+230
-67
lines changed

10 files changed

+230
-67
lines changed

docs/docs/icicle/primitives/keccak.md

+55-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,62 @@ At its core, Keccak consists of a permutation function operating on a state arra
1414

1515
## Using Keccak
1616

17-
ICICLE Keccak supports batch hashing, which can be utilized for constructing a merkle tree.
17+
ICICLE Keccak supports batch hashing, which can be utilized for constructing a merkle tree or running multiple hashes in parallel.
1818

1919
### Supported Bindings
2020

2121
- [Golang](https://github.com/ingonyama-zk/icicle/tree/main/wrappers/golang/hash/keccak)
22-
- [Rust](https://github.com/ingonyama-zk/icicle/tree/main/wrappers/rust/icicle-hash)
22+
- [Rust](https://github.com/ingonyama-zk/icicle/tree/main/wrappers/rust/icicle-hash)
23+
24+
### Example usage
25+
26+
This is an example of running 1024 Keccak-256 hashes in parallel, where input strings are of size 136 bytes:
27+
28+
```rust
29+
use icicle_core::hash::HashConfig;
30+
use icicle_cuda_runtime::memory::HostSlice;
31+
use icicle_hash::keccak::keccak256;
32+
33+
let config = HashConfig::default();
34+
let input_block_len = 136;
35+
let number_of_hashes = 1024;
36+
37+
let preimages = vec![1u8; number_of_hashes * input_block_len];
38+
let mut digests = vec![0u8; number_of_hashes * 64];
39+
40+
let preimages_slice = HostSlice::from_slice(&preimages);
41+
let digests_slice = HostSlice::from_mut_slice(&mut digests);
42+
43+
keccak256(
44+
preimages_slice,
45+
input_block_len as u32,
46+
number_of_hashes as u32,
47+
digests_slice,
48+
&config,
49+
)
50+
.unwrap();
51+
```
52+
53+
### Merkle Tree
54+
55+
You can build a keccak merkle tree using the corresponding functions:
56+
57+
```rust
58+
use icicle_core::tree::{merkle_tree_digests_len, TreeBuilderConfig};
59+
use icicle_cuda_runtime::memory::HostSlice;
60+
use icicle_hash::keccak::build_keccak256_merkle_tree;
61+
62+
let mut config = TreeBuilderConfig::default();
63+
config.arity = 2;
64+
let height = 22;
65+
let input_block_len = 136;
66+
let leaves = vec![1u8; (1 << height) * input_block_len];
67+
let mut digests = vec![0u64; merkle_tree_digests_len((height + 1) as u32, 2, 1)];
68+
69+
let leaves_slice = HostSlice::from_slice(&leaves);
70+
let digests_slice = HostSlice::from_mut_slice(&mut digests);
71+
72+
build_keccak256_merkle_tree(leaves_slice, digests_slice, height, input_block_len, &config).unwrap();
73+
```
74+
75+
In the example above, a binary tree of height 22 is being built. Each leaf is considered to be a 136 byte long array. The leaves and digests are aligned in a flat array. You can also use keccak512 in `build_keccak512_merkle_tree` function.

docs/docs/icicle/primitives/poseidon.md

+44-45
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ So for Poseidon of arity 2 and input of size 1024 * 2, we would expect 1024 elem
5353

5454
### Supported Bindings
5555

56+
[`Go`](https://github.com/ingonyama-zk/icicle/blob/main/wrappers/golang/curves/bn254/poseidon/poseidon.go)
5657
[`Rust`](https://github.com/ingonyama-zk/icicle/tree/main/wrappers/rust/icicle-core/src/poseidon)
5758

5859
### Constants
@@ -91,8 +92,6 @@ primitive_element = 7 # bls12-381
9192
# primitive_element = 15 # bw6-761
9293
```
9394

94-
We only support `alpha = 5` so if you want to use another alpha for S-box please reach out on discord or open a github issue.
95-
9695
### Rust API
9796

9897
This is the most basic way to use the Poseidon API.
@@ -101,71 +100,58 @@ This is the most basic way to use the Poseidon API.
101100
let test_size = 1 << 10;
102101
let arity = 2u32;
103102
let ctx = get_default_device_context();
104-
let constants = load_optimized_poseidon_constants::<F>(arity, &ctx).unwrap();
105-
let config = PoseidonConfig::default();
103+
let poseidon = Poseidon::load(arity, &ctx).unwrap();
104+
let config = HashConfig::default();
106105

107106
let inputs = vec![F::one(); test_size * arity as usize];
108107
let outputs = vec![F::zero(); test_size];
109108
let mut input_slice = HostOrDeviceSlice::on_host(inputs);
110109
let mut output_slice = HostOrDeviceSlice::on_host(outputs);
111110

112-
poseidon_hash_many::<F>(
111+
poseidon.hash_many::<F>(
113112
&mut input_slice,
114113
&mut output_slice,
115114
test_size as u32,
116115
arity as u32,
117-
&constants,
116+
1, // Output length
118117
&config,
119118
)
120119
.unwrap();
121120
```
122121

123-
The `PoseidonConfig::default()` can be modified, by default the inputs and outputs are set to be on `Host` for example.
122+
The `HashConfig` can be modified, by default the inputs and outputs are set to be on `Host` for example.
124123

125124
```rust
126-
impl<'a> Default for PoseidonConfig<'a> {
125+
impl<'a> Default for HashConfig<'a> {
127126
fn default() -> Self {
128127
let ctx = get_default_device_context();
129128
Self {
130129
ctx,
131130
are_inputs_on_device: false,
132131
are_outputs_on_device: false,
133-
input_is_a_state: false,
134-
aligned: false,
135-
loop_state: false,
136132
is_async: false,
137133
}
138134
}
139135
}
140136
```
141137

142-
In the example above `load_optimized_poseidon_constants::<F>(arity, &ctx).unwrap();` is used which will load the correct constants based on arity and curve. Its possible to [generate](#constants) your own constants and load them.
138+
In the example above `Poseidon::load(arity, &ctx).unwrap();` is used which will load the correct constants based on arity and curve. Its possible to [generate](#constants) your own constants and load them.
143139

144140
```rust
145141
let ctx = get_default_device_context();
146-
let cargo_manifest_dir = env!("CARGO_MANIFEST_DIR");
147-
let constants_file = PathBuf::from(cargo_manifest_dir)
148-
.join("tests")
149-
.join(format!("{}_constants.bin", field_prefix));
150-
let mut constants_buf = vec![];
151-
File::open(constants_file)
152-
.unwrap()
153-
.read_to_end(&mut constants_buf)
154-
.unwrap();
155-
156-
let mut custom_constants = vec![];
157-
for chunk in constants_buf.chunks(field_bytes) {
158-
custom_constants.push(F::from_bytes_le(chunk));
159-
}
160-
161-
let custom_constants = create_optimized_poseidon_constants::<F>(
162-
arity as u32,
163-
&ctx,
164-
full_rounds_half,
165-
partial_rounds,
166-
&mut custom_constants,
167-
)
168-
.unwrap();
142+
let custom_poseidon = Poseidon::new(
143+
arity, // The arity of poseidon hash. The width will be equal to arity + 1
144+
alpha, // The S-box power
145+
full_rounds_half,
146+
partial_rounds,
147+
round_constants,
148+
mds_matrix,
149+
non_sparse_matrix,
150+
sparse_matrices,
151+
domain_tag,
152+
ctx,
153+
)
154+
.unwrap();
169155
```
170156

171157
## The Tree Builder
@@ -175,21 +161,34 @@ The tree builder allows you to build Merkle trees using Poseidon.
175161
You can define both the tree's `height` and its `arity`. The tree `height` determines the number of layers in the tree, including the root and the leaf layer. The `arity` determines how many children each internal node can have.
176162

177163
```rust
178-
let height = 20;
164+
use icicle_bn254::tree::Bn254TreeBuilder;
165+
use icicle_bn254::poseidon::Poseidon;
166+
167+
let mut config = TreeBuilderConfig::default();
179168
let arity = 2;
180-
let leaves = vec![F::one(); 1 << (height - 1)];
181-
let mut digests = vec![F::zero(); merkle_tree_digests_len(height, arity)];
169+
config.arity = arity as u32;
170+
let input_block_len = arity;
171+
let leaves = vec![F::one(); (1 << height) * arity];
172+
let mut digests = vec![F::zero(); merkle_tree_digests_len((height + 1) as u32, arity as u32, 1)];
182173

183-
let mut leaves_slice = HostOrDeviceSlice::on_host(leaves);
174+
let leaves_slice = HostSlice::from_slice(&leaves);
175+
let digests_slice = HostSlice::from_mut_slice(&mut digests);
184176

185-
let ctx = get_default_device_context();
186-
let constants = load_optimized_poseidon_constants::<F>(arity, &ctx).unwrap()
177+
let ctx = device_context::DeviceContext::default();
178+
let hash = Poseidon::load(2, &ctx).unwrap();
187179

188180
let mut config = TreeBuilderConfig::default();
189-
config.keep_rows = 1;
190-
build_poseidon_merkle_tree::<F>(&mut leaves_slice, &mut digests, height, arity, &constants, &config).unwrap();
191-
192-
println!("Root: {:?}", digests[0..1][0]);
181+
config.keep_rows = 5;
182+
Bn254TreeBuilder::build_merkle_tree(
183+
leaves_slice,
184+
digests_slice,
185+
height,
186+
input_block_len,
187+
&hash,
188+
&hash,
189+
&config,
190+
)
191+
.unwrap();
193192
```
194193

195194
Similar to Poseidon, you can also configure the Tree Builder `TreeBuilderConfig::default()`
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Poseidon2
2+
3+
[Poseidon2](https://eprint.iacr.org/2023/323) is a recently released optimized version of Poseidon1. The two versions differ in two crucial points. First, Poseidon is a sponge hash function, while Poseidon2 can be either a sponge or a compression function depending on the use case. Secondly, Poseidon2 is instantiated by new and more efficient linear layers with respect to Poseidon. These changes decrease the number of multiplications in the linear layer by up to 90% and the number of constraints in Plonk circuits by up to 70%. This makes Poseidon2 currently the fastest arithmetization-oriented hash function without lookups.
4+
5+
6+
## Using Poseidon2
7+
8+
ICICLE Poseidon2 is implemented for GPU and parallelization is performed for each state.
9+
We calculate multiple hash-sums over multiple pre-images in parallel, rather than going block by block over the input vector.
10+
11+
For example, for Poseidon2 of width 16, input rate 8, output elements 8 and input of size 1024 * 8, we would expect 1024 * 8 elements of output. Which means each input block would be of size 8, resulting in 1024 Poseidon2 hashes being performed.
12+
13+
### Supported Bindings
14+
15+
[`Rust`](https://github.com/ingonyama-zk/icicle/tree/main/wrappers/rust/icicle-core/src/poseidon2)
16+
17+
### Constants
18+
19+
Poseidon2 is also extremely customizable and using different constants will produce different hashes, security levels and performance results.
20+
21+
We support pre-calculated constants for each of the [supported curves](../core#supported-curves-and-operations). The constants can be found [here](https://github.com/ingonyama-zk/icicle/tree/main/icicle/include/poseidon2/constants) and are labeled clearly per curve `<curve_name>_poseidon2.h`.
22+
23+
You can also use your own set of constants as shown [here](https://github.com/ingonyama-zk/icicle/blob/main/wrappers/rust/icicle-fields/icicle-babybear/src/poseidon2/mod.rs#L290)
24+
25+
### Rust API
26+
27+
This is the most basic way to use the Poseidon2 API.
28+
29+
```rust
30+
let test_size = 1 << 10;
31+
let width = 16;
32+
let rate = 8;
33+
let ctx = get_default_device_context();
34+
let poseidon = Poseidon2::load(width, rate, MdsType::Default, DiffusionStrategy::Default, &ctx).unwrap();
35+
let config = HashConfig::default();
36+
37+
let inputs = vec![F::one(); test_size * rate as usize];
38+
let outputs = vec![F::zero(); test_size];
39+
let mut input_slice = HostOrDeviceSlice::on_host(inputs);
40+
let mut output_slice = HostOrDeviceSlice::on_host(outputs);
41+
42+
poseidon.hash_many::<F>(
43+
&mut input_slice,
44+
&mut output_slice,
45+
test_size as u32,
46+
rate as u32,
47+
8, // Output length
48+
&config,
49+
)
50+
.unwrap();
51+
```
52+
53+
In the example above `Poseidon2::load(width, rate, MdsType::Default, DiffusionStrategy::Default, &ctx).unwrap();` is used to load the correct constants based on width and curve. Here, the default MDS matrices and diffusion are used. If you want to get a Plonky3 compliant version, set them to `MdsType::Plonky` and `DiffusionStrategy::Montgomery` respectively.
54+
55+
## The Tree Builder
56+
57+
Similar to Poseidon1, you can use Poseidon2 in a tree builder.
58+
59+
```rust
60+
use icicle_bn254::tree::Bn254TreeBuilder;
61+
use icicle_bn254::poseidon2::Poseidon2;
62+
63+
let mut config = TreeBuilderConfig::default();
64+
let arity = 2;
65+
config.arity = arity as u32;
66+
let input_block_len = arity;
67+
let leaves = vec![F::one(); (1 << height) * arity];
68+
let mut digests = vec![F::zero(); merkle_tree_digests_len((height + 1) as u32, arity as u32, 1)];
69+
70+
let leaves_slice = HostSlice::from_slice(&leaves);
71+
let digests_slice = HostSlice::from_mut_slice(&mut digests);
72+
73+
let ctx = device_context::DeviceContext::default();
74+
let hash = Poseidon2::load(arity, arity, MdsType::Default, DiffusionStrategy::Default, &ctx).unwrap();
75+
76+
let mut config = TreeBuilderConfig::default();
77+
config.keep_rows = 5;
78+
Bn254TreeBuilder::build_merkle_tree(
79+
leaves_slice,
80+
digests_slice,
81+
height,
82+
input_block_len,
83+
&hash,
84+
&hash,
85+
&config,
86+
)
87+
.unwrap();
88+
```

docs/package-lock.json

+12-7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/sidebars.js

+5
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ module.exports = {
5353
label: "Poseidon Hash",
5454
id: "icicle/primitives/poseidon",
5555
},
56+
{
57+
type: "doc",
58+
label: "Poseidon2 Hash",
59+
id: "icicle/primitives/poseidon2",
60+
},
5661
],
5762
},
5863
{

icicle/include/hash/keccak/keccak.cuh

+12-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@
1111
using namespace hash;
1212

1313
namespace keccak {
14+
// Input rate in bytes
15+
const int KECCAK_256_RATE = 136;
16+
const int KECCAK_512_RATE = 72;
17+
18+
// Digest size in u64
19+
const int KECCAK_256_DIGEST = 4;
20+
const int KECCAK_512_DIGEST = 8;
21+
22+
// Number of state elements in u64
23+
const int KECCAK_STATE_SIZE = 25;
24+
1425
class Keccak : public Hasher<uint8_t, uint64_t>
1526
{
1627
public:
@@ -22,7 +33,7 @@ namespace keccak {
2233
unsigned int output_len,
2334
const device_context::DeviceContext& ctx) const override;
2435

25-
Keccak(unsigned int rate) : Hasher<uint8_t, uint64_t>(25, 25, rate, 0) {}
36+
Keccak(unsigned int rate) : Hasher<uint8_t, uint64_t>(KECCAK_STATE_SIZE, KECCAK_STATE_SIZE, rate, 0) {}
2637
};
2738
} // namespace keccak
2839

0 commit comments

Comments
 (0)