Skip to content

Commit

Permalink
Poseidon support for hashes.
Browse files Browse the repository at this point in the history
  • Loading branch information
martun committed Dec 15, 2023
1 parent 0230aa9 commit 406c7b8
Show file tree
Hide file tree
Showing 14 changed files with 333 additions and 102 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/publish-results.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ on:
jobs:
call-reusable-workflow:
name: Call Reusable Testing Callback Workflow
uses: NilFoundation/ci-cd/.github/workflows/reusable-crypto3-publish-result.yml@v1
uses: NilFoundation/ci-cd/.github/workflows/reusable-crypto3-publish-result.yml@v1.2.0
43 changes: 43 additions & 0 deletions .github/workflows/pull-request-action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Reusable PR testing for mac and linux

on:
workflow_call:
inputs:
targets:
type: string
description: "Make and CTest targets. If not specified, everything is tested"
required: false
default: ""

jobs:
handle-syncwith:
name: Call Reusable SyncWith Handler
uses: NilFoundation/ci-cd/.github/workflows/reusable-handle-syncwith.yml@v1.2.0
with:
ci-cd-ref: 'v1.2.0'
secrets: inherit

matrix-test-linux:
name: Linux Reusable Crypto3 Testing
needs:
- handle-syncwith
uses: NilFoundation/ci-cd/.github/workflows/reusable-crypto3-testing-linux.yml@v1.2.0

secrets: inherit
with:
submodules-refs: ${{ needs.handle-syncwith.outputs.prs-refs }}
targets: ${{ inputs.targets }}
concurrency: 1 # hash tests take too much RAM.

# Temporarily disable mac tests, they fail.
# matrix-test-mac:
# name: Mac Reusable Crypto3 Testing
# needs:
# - handle-syncwith
# uses: NilFoundation/ci-cd/.github/workflows/reusable-crypto3-testing-mac.yml@v1.2.0
#
# secrets: inherit
# with:
# submodules-refs: ${{ needs.handle-syncwith.outputs.prs-refs }}
# targets: ${{ inputs.targets }}
# concurrency: 1 # hash tests take too much RAM.
16 changes: 3 additions & 13 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,8 @@ on:
- synchronize

jobs:
handle-syncwith:
name: Call Reusable SyncWith Handler
uses: NilFoundation/ci-cd/.github/workflows/reusable-handle-syncwith.yml@v1
with:
ci-cd-ref: 'v1'
secrets: inherit
run-pull-request-actions:
name: Reusable Crypto3 Testing
uses: ./.github/workflows/pull-request-action.yml

matrix-test:
name: Call Reusable Crypto3 Testing
needs:
- handle-syncwith
uses: NilFoundation/ci-cd/.github/workflows/reusable-crypto3-testing.yml@v1
with:
submodules-refs: ${{ needs.handle-syncwith.outputs.prs-refs }}
secrets: inherit
30 changes: 30 additions & 0 deletions .github/workflows/set_version.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Set version

on:
# Triggers the workflow on push to master branch
push:
branches: [ master ]

jobs:
set_version:
name: Set and tag version
runs-on: [ubuntu-latest]
env:
VERSION_FILE_NAME: VERSION
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Set version
id: set_version
run: |
version=$(cat ${{ env.VERSION_FILE_NAME }} | tr -d '\r').$GITHUB_RUN_NUMBER
echo "VERSION=$version" >> $GITHUB_ENV
- name: Tag new version
run: git tag v${{ env.VERSION }}

- name: Push tags
uses: ad-m/github-push-action@master
with:
tags: true
173 changes: 164 additions & 9 deletions include/nil/crypto3/hash/algorithm/hash.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
#include <nil/crypto3/hash/hash_value.hpp>
#include <nil/crypto3/hash/hash_state.hpp>

#include <nil/crypto3/hash/type_traits.hpp>
#include <nil/crypto3/hash/poseidon.hpp>
#include <nil/crypto3/hash/detail/poseidon/poseidon_sponge.hpp>
#include <nil/crypto3/hash/detail/poseidon/poseidon_policy.hpp>

#include <nil/crypto3/detail/type_traits.hpp>
#endif

Expand Down Expand Up @@ -82,7 +87,8 @@ namespace nil {
*
* @return
*/
template<typename Hash, typename InputIterator, typename OutputIterator>
template<typename Hash, typename InputIterator, typename OutputIterator,
std::enable_if_t<!crypto3::hashes::is_poseidon<Hash>::value, bool> = true>
typename std::enable_if<!boost::accumulators::detail::is_accumulator_set<OutputIterator>::value,
OutputIterator>::type
hash(InputIterator first, InputIterator last, OutputIterator out) {
Expand All @@ -95,6 +101,20 @@ namespace nil {
return HashImpl(first, last, std::move(out), HashAccumulator());
}

// For Posseidon. Refactor this later.
template<typename Hash, typename InputIterator, typename OutputIterator,
std::enable_if_t<crypto3::hashes::is_poseidon<Hash>::value, bool> = true>
OutputIterator hash(InputIterator first, InputIterator last, OutputIterator out) {
hashes::detail::poseidon_sponge_construction<typename Hash::policy_type> sponge;

while (first != last) {
sponge.absorb(*first++);
}
*out = sponge.squeeze();
++out;
return out;
}

/*!
* @brief
*
Expand All @@ -110,7 +130,8 @@ namespace nil {
*
* @return
*/
template<typename Hash, typename InputIterator, typename HashAccumulator = accumulator_set<Hash>>
template<typename Hash, typename InputIterator, typename HashAccumulator = accumulator_set<Hash>,
std::enable_if_t<!crypto3::hashes::is_poseidon<Hash>::value, bool> = true>
typename std::enable_if<boost::accumulators::detail::is_accumulator_set<HashAccumulator>::value,
HashAccumulator>::type
hash(InputIterator first, InputIterator last, HashAccumulator &sh) {
Expand All @@ -135,7 +156,8 @@ namespace nil {
*
* @return
*/
template<typename Hash, typename InputIterator, typename HashAccumulator = accumulator_set<Hash>>
template<typename Hash, typename InputIterator, typename HashAccumulator = accumulator_set<Hash>,
std::enable_if_t<!crypto3::hashes::is_poseidon<Hash>::value, bool> = true>
hashes::detail::range_hash_impl<hashes::detail::value_hash_impl<typename std::enable_if<
boost::accumulators::detail::is_accumulator_set<HashAccumulator>::value, HashAccumulator>::type>>
hash(InputIterator first, InputIterator last) {
Expand All @@ -146,6 +168,21 @@ namespace nil {

return HashImpl(first, last, HashAccumulator());
}

// For posseidon.
template<typename Hash, typename InputIterator,
std::enable_if_t<crypto3::hashes::is_poseidon<Hash>::value &&
nil::crypto3::detail::is_iterator<InputIterator>::value, bool> = true>
typename Hash::digest_type hash(InputIterator first, InputIterator last) {

hashes::detail::poseidon_sponge_construction<typename Hash::policy_type> sponge;

while (first != last) {
sponge.absorb(*first++);
}
return sponge.squeeze();

}

/*!
* @brief
Expand All @@ -161,7 +198,8 @@ namespace nil {
*
* @return
*/
template<typename Hash, typename SinglePassRange, typename OutputIterator>
template<typename Hash, typename SinglePassRange, typename OutputIterator,
std::enable_if_t<!crypto3::hashes::is_poseidon<Hash>::value, bool> = true>
typename std::enable_if<::nil::crypto3::detail::is_iterator<OutputIterator>::value, OutputIterator>::type
hash(const SinglePassRange &rng, OutputIterator out) {

Expand All @@ -187,7 +225,8 @@ namespace nil {
*
* @return
*/
template<typename Hash, typename SinglePassRange, typename HashAccumulator = accumulator_set<Hash>>
template<typename Hash, typename SinglePassRange, typename HashAccumulator = accumulator_set<Hash>,
std::enable_if_t<!crypto3::hashes::is_poseidon<Hash>::value, bool> = true>
typename std::enable_if<boost::accumulators::detail::is_accumulator_set<HashAccumulator>::value,
HashAccumulator>::type
hash(const SinglePassRange &rng, HashAccumulator &sh) {
Expand All @@ -211,7 +250,8 @@ namespace nil {
*
* @return
*/
template<typename Hash, typename SinglePassRange, typename HashAccumulator = accumulator_set<Hash>>
template<typename Hash, typename SinglePassRange, typename HashAccumulator = accumulator_set<Hash>,
std::enable_if_t<!crypto3::hashes::is_poseidon<Hash>::value, bool> = true>
hashes::detail::range_hash_impl<hashes::detail::value_hash_impl<HashAccumulator>>
hash(const SinglePassRange &r) {

Expand All @@ -221,6 +261,118 @@ namespace nil {
return HashImpl(r, HashAccumulator());
}

// TODO: Use packing in the block_stream_processor in the future, now it will work well with 256 bit
// field elements which are used in posseidon. Also try to use concepts, not fix the posseidon class type.

// This function is used for merkle tree, where multiple group elements are hashed together to create the parent element.
template<typename Hash, typename GroupElementsContainer,
std::enable_if_t<crypto3::hashes::is_poseidon<Hash>::value &&
std::is_same<typename Hash::digest_type, typename GroupElementsContainer::value_type>::value, bool> = true>
typename Hash::digest_type hash(const GroupElementsContainer &r, const typename Hash::digest_type& initial_element) {

hashes::detail::poseidon_sponge_construction<typename Hash::policy_type> sponge;

sponge.absorb(initial_element);

for (const auto& element: r) {
sponge.absorb(element);
}
return sponge.squeeze();
}

// This function is used for merkle tree, where multiple group elements are hashed together to create the parent element.
template<typename Hash, typename GroupElementsContainer,
std::enable_if_t<crypto3::hashes::is_poseidon<Hash>::value &&
std::is_same<typename Hash::digest_type, typename GroupElementsContainer::value_type>::value, bool> = true>
typename Hash::digest_type hash(const GroupElementsContainer &r) {

hashes::detail::poseidon_sponge_construction<typename Hash::policy_type> sponge;

for (const auto& element: r) {
sponge.absorb(element);
}
return sponge.squeeze();
}

// This function is used for merkle tree to initially hash the original elements in the tree leaves.
template<typename Hash, typename GroupElement,
std::enable_if_t<crypto3::hashes::is_poseidon<Hash>::value &&
std::is_same<typename Hash::digest_type, GroupElement>::value, bool> = true>
typename Hash::digest_type hash(const GroupElement &element, const typename Hash::digest_type& initial_element) {

hashes::detail::poseidon_sponge_construction<typename Hash::policy_type> sponge;

sponge.absorb(initial_element);
sponge.absorb(element);

return sponge.squeeze();
}

template<typename Hash, typename GroupElement,
std::enable_if_t<crypto3::hashes::is_poseidon<Hash>::value &&
std::is_same<typename Hash::digest_type, GroupElement>::value, bool> = true>
typename Hash::digest_type hash(const GroupElement &element) {

hashes::detail::poseidon_sponge_construction<typename Hash::policy_type> sponge;

sponge.absorb(element);

return sponge.squeeze();
}

// This function is used for hashing containers of integral values using Posseidon hash.
// Usually this will pack a vector of 8-bit integers into a 255 bit group element, which means the last
// 7 bits will not be used in the current implementation. Also group element multiplications are pretty slow.
template<typename Hash, typename IntegralContainer,
std::enable_if_t<crypto3::hashes::is_poseidon<Hash>::value &&
std::is_integral<typename IntegralContainer::value_type>::value, bool> = true>
typename Hash::digest_type absorb(hashes::detail::poseidon_sponge_construction<typename Hash::policy_type>& sponge,
const IntegralContainer &r) {
typename Hash::digest_type next_element = Hash::digest_type::zero();
std::size_t bits_left = Hash::word_bits;

std::size_t input_word_size = CHAR_BIT * sizeof(typename IntegralContainer::value_type);

// At least 1 input word must fit in a single group element.
// Normally group element is 255 or 256 bits, while input word size is 8 or 32 or 64 bits.
assert(input_word_size <= Hash::word_bits);

for (const auto& word: r) {
if (bits_left < input_word_size) {
sponge.absorb(next_element);
next_element = Hash::digest_type::zero();
bits_left = Hash::word_bits;
}
next_element *= 1 << input_word_size;
next_element += word;
bits_left -= input_word_size;
}

if (bits_left != Hash::word_bits) {
sponge.absorb(next_element);
}

return sponge.squeeze();
}

template<typename Hash, typename IntegralContainer,
std::enable_if_t<crypto3::hashes::is_poseidon<Hash>::value &&
std::is_integral<typename IntegralContainer::value_type>::value, bool> = true>
typename Hash::digest_type hash(const IntegralContainer &r, const typename Hash::digest_type& initial_element) {
hashes::detail::poseidon_sponge_construction<typename Hash::policy_type> sponge;

sponge.absorb(initial_element);
return absorb<Hash>(sponge, r);
}

template<typename Hash, typename IntegralContainer,
std::enable_if_t<crypto3::hashes::is_poseidon<Hash>::value &&
std::is_integral<typename IntegralContainer::value_type>::value, bool> = true>
typename Hash::digest_type hash(const IntegralContainer &r) {
hashes::detail::poseidon_sponge_construction<typename Hash::policy_type> sponge;
return absorb<Hash>(sponge, r);
}

/*!
* @brief
*
Expand All @@ -235,7 +387,8 @@ namespace nil {
*
* @return
*/
template<typename Hash, typename T, typename OutputIterator>
template<typename Hash, typename T, typename OutputIterator,
std::enable_if_t<!crypto3::hashes::is_poseidon<Hash>::value, bool> = true>
typename std::enable_if<::nil::crypto3::detail::is_iterator<OutputIterator>::value, OutputIterator>::type
hash(std::initializer_list<T> list, OutputIterator out) {

Expand All @@ -261,7 +414,8 @@ namespace nil {
*
* @return
*/
template<typename Hash, typename T, typename HashAccumulator = accumulator_set<Hash>>
template<typename Hash, typename T, typename HashAccumulator = accumulator_set<Hash>,
std::enable_if_t<!crypto3::hashes::is_poseidon<Hash>::value, bool> = true>
typename std::enable_if<boost::accumulators::detail::is_accumulator_set<HashAccumulator>::value,
HashAccumulator>::type
hash(std::initializer_list<T> rng, HashAccumulator &sh) {
Expand All @@ -285,7 +439,8 @@ namespace nil {
*
* @return
*/
template<typename Hash, typename T, typename HashAccumulator = accumulator_set<Hash>>
template<typename Hash, typename T, typename HashAccumulator = accumulator_set<Hash>,
std::enable_if_t<!crypto3::hashes::is_poseidon<Hash>::value, bool> = true>
hashes::detail::range_hash_impl<hashes::detail::value_hash_impl<HashAccumulator>>
hash(std::initializer_list<T> r) {

Expand Down
3 changes: 1 addition & 2 deletions include/nil/crypto3/hash/detail/block_stream_processor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,10 +76,9 @@ namespace nil {
BOOST_STATIC_ASSERT(block_bits % value_bits == 0);

inline void process_block(std::size_t block_seen = block_bits) {
using namespace nil::crypto3::detail;
// Convert the input into words
block_type block;
pack_to<endian_type, value_bits, word_bits>(cache.begin(), cache.end(), block.begin());
nil::crypto3::detail::pack_to<endian_type, value_bits, word_bits>(cache.begin(), cache.end(), block.begin());
// Process the block
acc(block, ::nil::crypto3::accumulators::bits = block_seen);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ namespace nil {
// Choose which constants we want, original or kimchi. We may later add
// other sets of constants here.
typedef typename std::conditional<poseidon_policy_type::mina_version, poseidon_kimchi_constants_data<policy_type>, poseidon_original_constants_data<policy_type>>::type constants_data_type;

poseidon_constants() {
// Transpose the matrix.
for (std::size_t i = 0; i < state_words; i++) {
Expand Down
Loading

0 comments on commit 406c7b8

Please sign in to comment.