diff --git a/tachyon/math/finite_fields/generator/prime_field_generator/small_prime_field_config.h.tpl b/tachyon/math/finite_fields/generator/prime_field_generator/small_prime_field_config.h.tpl index ec83db6d0..59b0895f1 100644 --- a/tachyon/math/finite_fields/generator/prime_field_generator/small_prime_field_config.h.tpl +++ b/tachyon/math/finite_fields/generator/prime_field_generator/small_prime_field_config.h.tpl @@ -3,6 +3,7 @@ #include "tachyon/export.h" #include "tachyon/build/build_config.h" +#include "tachyon/math/base/big_int.h" namespace %{namespace} { @@ -63,6 +64,10 @@ class TACHYON_EXPORT %{class}Config { return (uint64_t{v} << 32) % kModulus; } + constexpr static uint32_t ToMontgomery(uint64_t v) { + return static_cast(((tachyon::math::BigInt<2>(v) << 32) % tachyon::math::BigInt<2>(kModulus))[0]); + } + constexpr static uint32_t FromMontgomery(uint64_t v) { constexpr uint64_t kMask = (uint64_t{1} << 32) - 1; uint64_t t = (v * kInverse32) & kMask; diff --git a/tachyon/zk/air/plonky3/base/BUILD.bazel b/tachyon/zk/air/plonky3/base/BUILD.bazel new file mode 100644 index 000000000..dc788f7b4 --- /dev/null +++ b/tachyon/zk/air/plonky3/base/BUILD.bazel @@ -0,0 +1,13 @@ +load("//bazel:tachyon_cc.bzl", "tachyon_cc_library") + +package(default_visibility = ["//visibility:public"]) + +tachyon_cc_library( + name = "multi_field32_conversions", + hdrs = ["multi_field32_conversions.h"], + deps = [ + "//tachyon/base/containers:adapters", + "//tachyon/build:build_config", + "@com_google_absl//absl/types:span", + ], +) diff --git a/tachyon/zk/air/plonky3/base/multi_field32_conversions.h b/tachyon/zk/air/plonky3/base/multi_field32_conversions.h new file mode 100644 index 000000000..4ebfcacce --- /dev/null +++ b/tachyon/zk/air/plonky3/base/multi_field32_conversions.h @@ -0,0 +1,73 @@ +// Copyright (c) 2022 The Plonky3 Authors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.plonky3 and the LICENCE-APACHE.plonky3 +// file. + +#ifndef TACHYON_ZK_AIR_PLONKY3_BASE_MULTI_FIELD32_CONVERSIONS_H_ +#define TACHYON_ZK_AIR_PLONKY3_BASE_MULTI_FIELD32_CONVERSIONS_H_ + +#include + +#include +#include + +#include "absl/types/span.h" + +#include "tachyon/base/containers/adapters.h" +#include "tachyon/build/build_config.h" + +namespace tachyon::zk::air::plonky3 { + +template +BigF Reduce(absl::Span values) { + static_assert(SmallF::Config::kModulusBits <= 32); + static_assert(BigF::Config::kModulusBits > 64); + + using BigInt = typename BigF::BigIntTy; + CHECK_LT(values.size(), BigInt::kLimbNums * 2); + + BigInt ret; + for (size_t i = 0; i < values.size(); i += 2) { + uint32_t value = values[i].value(); + if constexpr (SmallF::Config::kUseMontgomery) { + ret[i >> 1] = SmallF::Config::FromMontgomery(value); + } else { + ret[i >> 1] = value; + } + if (i < values.size() - 1) { + uint64_t value2 = values[i + 1].value(); + if constexpr (SmallF::Config::kUseMontgomery) { + ret[i >> 1] += uint64_t{SmallF::Config::FromMontgomery(value2)} << 32; + } else { + ret[i >> 1] += value2 << 32; + } + } + } + return BigF(ret % BigF::Config::kModulus); +} + +template +std::array Split(const BigF& value) { + static_assert(SmallF::Config::kModulusBits <= 32); + static_assert(BigF::Config::kModulusBits > 64); + static_assert(ARCH_CPU_LITTLE_ENDIAN); + + using BigInt = typename BigF::BigIntTy; + std::array ret; + BigInt value_bigint = value.ToBigInt(); + for (size_t i = 0; i < N; ++i) { + uint64_t digit = value_bigint[0] & std::numeric_limits::max(); + if constexpr (SmallF::Config::kUseMontgomery) { + ret[i] = SmallF::FromMontgomery(SmallF::Config::ToMontgomery(digit)); + } else { + ret[i] = SmallF(digit); + } + value_bigint >>= 64; + } + return ret; +} + +} // namespace tachyon::zk::air::plonky3 + +#endif // TACHYON_ZK_AIR_PLONKY3_BASE_MULTI_FIELD32_CONVERSIONS_H_ diff --git a/tachyon/zk/air/plonky3/challenger/BUILD.bazel b/tachyon/zk/air/plonky3/challenger/BUILD.bazel new file mode 100644 index 000000000..a161c1251 --- /dev/null +++ b/tachyon/zk/air/plonky3/challenger/BUILD.bazel @@ -0,0 +1,71 @@ +load("//bazel:tachyon_cc.bzl", "tachyon_cc_library", "tachyon_cc_unittest") + +package(default_visibility = ["//visibility:public"]) + +tachyon_cc_library( + name = "challenger", + hdrs = ["challenger.h"], + deps = [ + ":challenger_traits_forward", + "//tachyon/base:logging", + "//tachyon/base:openmp_util", + "//tachyon/base:range", + "//tachyon/base/containers:container_util", + ], +) + +tachyon_cc_library( + name = "challenger_traits_forward", + hdrs = ["challenger_traits_forward.h"], +) + +tachyon_cc_library( + name = "duplex_challenger", + hdrs = ["duplex_challenger.h"], + deps = [ + ":challenger", + "//tachyon/crypto/hashes/sponge:sponge_state", + "@com_google_absl//absl/container:inlined_vector", + ], +) + +tachyon_cc_library( + name = "hash_challenger", + hdrs = ["hash_challenger.h"], + deps = [ + ":challenger", + "//tachyon/base/containers:container_util", + ], +) + +tachyon_cc_library( + name = "multi_field32_challenger", + hdrs = ["multi_field32_challenger.h"], + deps = [ + ":challenger", + "//tachyon/base/containers:container_util", + "//tachyon/crypto/hashes/sponge:sponge_state", + "//tachyon/zk/air/plonky3/base:multi_field32_conversions", + "@com_google_absl//absl/container:inlined_vector", + ], +) + +tachyon_cc_unittest( + name = "challenger_unittests", + srcs = [ + "duplex_challenger_unittest.cc", + "hash_challenger_unittest.cc", + "multi_field32_challenger_unittest.cc", + ], + deps = [ + ":duplex_challenger", + ":hash_challenger", + ":multi_field32_challenger", + "//tachyon/crypto/hashes/sponge:padding_free_sponge", + "//tachyon/crypto/hashes/sponge/poseidon2", + "//tachyon/crypto/hashes/sponge/poseidon2:poseidon2_plonky3_external_matrix", + "//tachyon/math/elliptic_curves/bn/bn254:poseidon2", + "//tachyon/math/finite_fields/baby_bear:poseidon2", + "//tachyon/math/finite_fields/test:finite_field_test", + ], +) diff --git a/tachyon/zk/air/plonky3/challenger/challenger.h b/tachyon/zk/air/plonky3/challenger/challenger.h new file mode 100644 index 000000000..b5225aed4 --- /dev/null +++ b/tachyon/zk/air/plonky3/challenger/challenger.h @@ -0,0 +1,126 @@ +// Copyright (c) 2022 The Plonky3 Authors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.plonky3 and the LICENCE-APACHE.plonky3 +// file. + +#ifndef TACHYON_ZK_AIR_PLONKY3_CHALLENGER_CHALLENGER_H_ +#define TACHYON_ZK_AIR_PLONKY3_CHALLENGER_CHALLENGER_H_ + +#include +#include + +#include +#include +#include +#include + +#include "tachyon/base/containers/container_util.h" +#include "tachyon/base/logging.h" +#include "tachyon/base/openmp_util.h" +#include "tachyon/base/range.h" +#include "tachyon/zk/air/plonky3/challenger/challenger_traits_forward.h" + +namespace tachyon::zk::air::plonky3 { + +template +class Challenger { + public: + using Field = typename ChallengerTraits::Field; + + template + void Observe(const T& value) { + Derived* derived = static_cast(this); + derived->DoObserve(value); + } + + template + void ObserveContainer(const Container& container) { + Derived* derived = static_cast(this); + for (const auto& value : container) { + derived->DoObserve(value); + } + } + + template + void ObserveContainer2D(const Container& container_2d) { + Derived* derived = static_cast(this); + for (const auto& container : container_2d) { + for (const auto& value : container) { + derived->DoObserve(value); + } + } + } + + Field Sample() { + Derived* derived = static_cast(this); + return derived->DoSample(); + } + + template + std::array SampleArray() { + return base::CreateArray(N, [this]() { return Sample(); }); + } + + template + ExtField SampleExtElement() { + constexpr size_t N = ExtField::kDegreeOverBasePrimeField; + using F = typename ExtField::BasePrimeField; + static_assert(std::is_same_v); + std::array prime_fields = SampleArray(); + return ExtField::FromBasePrimeFields(prime_fields); + } + + uint32_t SampleBits(uint32_t bits) { + static_assert(Field::Config::kModulusBits <= 32); + DCHECK_LT(bits, sizeof(uint32_t) * 8); + DCHECK_LT(uint32_t{1} << bits, Field::Config::kModulus); + Field rand_f = Sample(); + uint32_t rand_size; + if constexpr (Field::Config::kUseMontgomery) { + rand_size = Field::Config::FromMontgomery(rand_f.value()); + } else { + rand_size = rand_f.value(); + } + return rand_size & ((uint32_t{1} << bits) - 1); + } + + Field Grind(uint32_t bits) { + return Grind(bits, base::Range::Until(Field::Config::kModulus)); + } + + Field Grind(uint32_t bits, base::Range range) { +#if defined(TACHYON_HAS_OPENMP) + uint32_t thread_nums = static_cast(omp_get_max_threads()); +#else + uint32_t thread_nums = 1; +#endif + uint32_t chunk_size = range.GetSize() / thread_nums; + std::vector ret(thread_nums, + std::numeric_limits::max()); + OPENMP_PARALLEL_FOR(uint32_t i = 0; i < thread_nums; ++i) { + uint32_t start = range.from + i * chunk_size; + uint32_t end = start + std::min(range.to - start, chunk_size); + for (uint32_t j = start; j < end; ++j) { + Derived derived = *static_cast(this); + if (derived.CheckWitness(bits, Field(j))) { + ret[i] = j; + break; + } + } + } + auto it = std::find_if(ret.begin(), ret.end(), [](uint32_t v) { + return v != std::numeric_limits::max(); + }); + CHECK(it != ret.end()); + return Field(*it); + } + + bool CheckWitness(uint32_t bits, const Field& witness) { + Observe(witness); + return SampleBits(bits) == 0; + } +}; + +} // namespace tachyon::zk::air::plonky3 + +#endif // TACHYON_ZK_AIR_PLONKY3_CHALLENGER_CHALLENGER_H_ diff --git a/tachyon/zk/air/plonky3/challenger/challenger_traits_forward.h b/tachyon/zk/air/plonky3/challenger/challenger_traits_forward.h new file mode 100644 index 000000000..d992fa5d1 --- /dev/null +++ b/tachyon/zk/air/plonky3/challenger/challenger_traits_forward.h @@ -0,0 +1,16 @@ +// Copyright (c) 2022 The Plonky3 Authors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.plonky3 and the LICENCE-APACHE.plonky3 +// file. + +#ifndef TACHYON_ZK_AIR_PLONKY3_CHALLENGER_CHALLENGER_TRAITS_FORWARD_H_ +#define TACHYON_ZK_AIR_PLONKY3_CHALLENGER_CHALLENGER_TRAITS_FORWARD_H_ + +namespace tachyon::zk::air::plonky3 { + +template +struct ChallengerTraits; + +} // namespace tachyon::zk::air::plonky3 + +#endif // TACHYON_ZK_AIR_PLONKY3_CHALLENGER_CHALLENGER_TRAITS_FORWARD_H_ diff --git a/tachyon/zk/air/plonky3/challenger/duplex_challenger.h b/tachyon/zk/air/plonky3/challenger/duplex_challenger.h new file mode 100644 index 000000000..0e4091349 --- /dev/null +++ b/tachyon/zk/air/plonky3/challenger/duplex_challenger.h @@ -0,0 +1,78 @@ +// Copyright (c) 2022 The Plonky3 Authors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.plonky3 and the LICENCE-APACHE.plonky3 +// file. + +#ifndef TACHYON_ZK_AIR_PLONKY3_CHALLENGER_DUPLEX_CHALLENGER_H_ +#define TACHYON_ZK_AIR_PLONKY3_CHALLENGER_DUPLEX_CHALLENGER_H_ + +#include + +#include "absl/container/inlined_vector.h" + +#include "tachyon/crypto/hashes/sponge/sponge_state.h" +#include "tachyon/zk/air/plonky3/challenger/challenger.h" + +namespace tachyon::zk::air::plonky3 { + +template +class DuplexChallenger final + : public Challenger> { + public: + using F = typename Permutation::F; + + explicit DuplexChallenger(Permutation&& permutation) + : permutation_(std::move(permutation)) {} + + private: + friend class Challenger>; + + // Challenger methods + void DoObserve(const F& value) { + output_buffer_.clear(); + + input_buffer_.push_back(value); + + if (input_buffer_.size() == R) { + Duplex(); + } + } + + F DoSample() { + if (!input_buffer_.empty() || output_buffer_.empty()) { + Duplex(); + } + + F ret = std::move(output_buffer_.back()); + output_buffer_.pop_back(); + return ret; + } + + void Duplex() { + for (size_t i = 0; i < input_buffer_.size(); ++i) { + state_[i] = std::move(input_buffer_[i]); + } + input_buffer_.clear(); + + permutation_.Permute(state_); + + output_buffer_.clear(); + for (size_t i = 0; i < W; ++i) { + output_buffer_.push_back(state_[i]); + } + } + + crypto::SpongeState state_{W}; + absl::InlinedVector input_buffer_; + absl::InlinedVector output_buffer_; + Permutation permutation_; +}; + +template +struct ChallengerTraits> { + using Field = typename Permutation::F; +}; + +} // namespace tachyon::zk::air::plonky3 + +#endif // TACHYON_ZK_AIR_PLONKY3_CHALLENGER_DUPLEX_CHALLENGER_H_ diff --git a/tachyon/zk/air/plonky3/challenger/duplex_challenger_unittest.cc b/tachyon/zk/air/plonky3/challenger/duplex_challenger_unittest.cc new file mode 100644 index 000000000..d54f7f300 --- /dev/null +++ b/tachyon/zk/air/plonky3/challenger/duplex_challenger_unittest.cc @@ -0,0 +1,66 @@ +// Copyright (c) 2022 The Plonky3 Authors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.plonky3 and the LICENCE-APACHE.plonky3 +// file. + +#include "tachyon/zk/air/plonky3/challenger/duplex_challenger.h" + +#include + +#include "gtest/gtest.h" + +#include "tachyon/base/bits.h" +#include "tachyon/crypto/hashes/sponge/poseidon2/poseidon2.h" +#include "tachyon/crypto/hashes/sponge/poseidon2/poseidon2_plonky3_external_matrix.h" +#include "tachyon/math/finite_fields/baby_bear/poseidon2.h" +#include "tachyon/math/finite_fields/test/finite_field_test.h" + +namespace tachyon::zk::air::plonky3 { + +using F = math::BabyBear; +using Poseidon2 = crypto::Poseidon2Sponge< + crypto::Poseidon2ExternalMatrix>>; + +namespace { + +class DuplexChallengerTest : public math::FiniteFieldTest { + public: + constexpr static size_t kWidth = 16; + constexpr static size_t kRate = 4; + + void SetUp() override { + crypto::Poseidon2Config config = + crypto::Poseidon2Config::CreateCustom( + 15, 7, 8, 13, math::GetPoseidon2BabyBearInternalShiftVector<15>()); + Poseidon2 sponge(config); + challenger_.reset( + new DuplexChallenger(std::move(sponge))); + } + + protected: + std::unique_ptr> challenger_; +}; + +} // namespace + +TEST_F(DuplexChallengerTest, Sample) { + for (uint32_t i = 0; i < 20; ++i) { + challenger_->Observe(F(i)); + } + + F answers[] = { + F(1091695522), F(747772208), F(1145639564), F(1789312616), F(567623980), + F(179016966), F(125050365), F(1725901131), F(65962335), F(1086560956), + }; + for (size_t i = 0; i < std::size(answers); ++i) { + EXPECT_EQ(challenger_->Sample(), answers[i]); + } +} + +TEST_F(DuplexChallengerTest, Grind) { + const uint32_t kBits = 3; + F witness = challenger_->Grind(kBits, base::Range(0, 100)); + EXPECT_TRUE(challenger_->CheckWitness(kBits, witness)); +} + +} // namespace tachyon::zk::air::plonky3 diff --git a/tachyon/zk/air/plonky3/challenger/hash_challenger.h b/tachyon/zk/air/plonky3/challenger/hash_challenger.h new file mode 100644 index 000000000..14fbd717c --- /dev/null +++ b/tachyon/zk/air/plonky3/challenger/hash_challenger.h @@ -0,0 +1,68 @@ +// Copyright (c) 2022 The Plonky3 Authors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.plonky3 and the LICENCE-APACHE.plonky3 +// file. + +#ifndef TACHYON_ZK_AIR_PLONKY3_CHALLENGER_HASH_CHALLENGER_H_ +#define TACHYON_ZK_AIR_PLONKY3_CHALLENGER_HASH_CHALLENGER_H_ + +#include +#include + +#include "tachyon/base/containers/container_util.h" +#include "tachyon/zk/air/plonky3/challenger/challenger.h" + +namespace tachyon::zk::air::plonky3 { + +template +class HashChallenger final : public Challenger> { + public: + using F = typename Hasher::F; + + explicit HashChallenger(Hasher&& hasher) : hasher_(std::move(hasher)) {} + HashChallenger(const std::vector& input_buffer, Hasher&& hasher) + : input_buffer_(input_buffer), hasher_(std::move(hasher)) {} + HashChallenger(std::vector&& input_buffer, Hasher&& hasher) + : input_buffer_(std::move(input_buffer)), hasher_(std::move(hasher)) {} + + private: + friend class Challenger>; + + // Challenger methods + void DoObserve(const F& value) { + output_buffer_.clear(); + + input_buffer_.push_back(value); + } + + F DoSample() { + if (output_buffer_.empty()) { + Flush(); + } + + F ret = std::move(output_buffer_.back()); + output_buffer_.pop_back(); + return ret; + } + + void Flush() { + auto output = hasher_.Hash(input_buffer_); + + output_buffer_ = + base::Map(output, [](F& value) { return std::move(value); }); + input_buffer_ = output_buffer_; + } + + std::vector input_buffer_; + std::vector output_buffer_; + Hasher hasher_; +}; + +template +struct ChallengerTraits> { + using Field = typename Hasher::F; +}; + +} // namespace tachyon::zk::air::plonky3 + +#endif // TACHYON_ZK_AIR_PLONKY3_CHALLENGER_HASH_CHALLENGER_H_ diff --git a/tachyon/zk/air/plonky3/challenger/hash_challenger_unittest.cc b/tachyon/zk/air/plonky3/challenger/hash_challenger_unittest.cc new file mode 100644 index 000000000..af96f1fb6 --- /dev/null +++ b/tachyon/zk/air/plonky3/challenger/hash_challenger_unittest.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2022 The Plonky3 Authors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.plonky3 and the LICENCE-APACHE.plonky3 +// file. + +#include "tachyon/zk/air/plonky3/challenger/hash_challenger.h" + +#include + +#include "gtest/gtest.h" + +#include "tachyon/crypto/hashes/sponge/padding_free_sponge.h" +#include "tachyon/crypto/hashes/sponge/poseidon2/poseidon2.h" +#include "tachyon/crypto/hashes/sponge/poseidon2/poseidon2_plonky3_external_matrix.h" +#include "tachyon/math/finite_fields/baby_bear/poseidon2.h" +#include "tachyon/math/finite_fields/test/finite_field_test.h" + +namespace tachyon::zk::air::plonky3 { + +using F = math::BabyBear; +using Poseidon2 = crypto::Poseidon2Sponge< + crypto::Poseidon2ExternalMatrix>>; +using Hasher = crypto::PaddingFreeSponge; + +namespace { + +class HashChallengerTest : public math::FiniteFieldTest { + public: + void SetUp() override { + crypto::Poseidon2Config config = + crypto::Poseidon2Config::CreateCustom( + 15, 7, 8, 13, math::GetPoseidon2BabyBearInternalShiftVector<15>()); + Poseidon2 sponge(config); + Hasher hasher(std::move(sponge)); + + std::vector initial_state = + base::CreateVector(10, [](size_t i) { return F(i + 1); }); + challenger_.reset(new HashChallenger(std::move(initial_state), + std::move(hasher))); + } + + protected: + std::unique_ptr> challenger_; +}; + +} // namespace + +TEST_F(HashChallengerTest, Sample) { + for (uint32_t i = 0; i < 20; ++i) { + challenger_->Observe(F(i)); + } + + F answers[] = { + F(886174168), F(1457271233), F(1952268252), F(1595005924), F(796215768), + F(1553987485), F(1108393593), F(1336137665), F(971109448), F(1853357459), + }; + for (size_t i = 0; i < std::size(answers); ++i) { + EXPECT_EQ(challenger_->Sample(), answers[i]); + } +} + +// NOTE(chokobole): Grind is not tested since |HashChallenger| doesn't implement +// |GrindingChallenger| traits. +// See +// https://github.com/Plonky3/Plonky3/blob/7bb6db50594e159010f11c97d110aa3ee121069b/challenger/src/grinding_challenger.rs. + +} // namespace tachyon::zk::air::plonky3 diff --git a/tachyon/zk/air/plonky3/challenger/multi_field32_challenger.h b/tachyon/zk/air/plonky3/challenger/multi_field32_challenger.h new file mode 100644 index 000000000..ffcb1717d --- /dev/null +++ b/tachyon/zk/air/plonky3/challenger/multi_field32_challenger.h @@ -0,0 +1,103 @@ +// Copyright (c) 2022 The Plonky3 Authors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.plonky3 and the LICENCE-APACHE.plonky3 +// file. + +#ifndef TACHYON_ZK_AIR_PLONKY3_CHALLENGER_MULTI_FIELD32_CHALLENGER_H_ +#define TACHYON_ZK_AIR_PLONKY3_CHALLENGER_MULTI_FIELD32_CHALLENGER_H_ + +#include +#include + +#include "absl/container/inlined_vector.h" + +#include "tachyon/base/containers/container_util.h" +#include "tachyon/crypto/hashes/sponge/sponge_state.h" +#include "tachyon/zk/air/plonky3/base/multi_field32_conversions.h" +#include "tachyon/zk/air/plonky3/challenger/challenger.h" + +namespace tachyon::zk::air::plonky3 { + +// A challenger that operates natively on |BigF| but produces challenges of +// |SmallF|. +// +// Used for optimizing the cost of recursive proof verification of STARKs in +// SNARKs. +// +// SAFETY: There are some bias complications with using this challenger. In +// particular, samples are actually random in [0, 2⁶⁴) and then reduced to be +// in |SmallF|. +template +class MultiField32Challenger final + : public Challenger> { + public: + using BigF = typename Permutation::F; + + static_assert(BigF::Config::kModulusBits > 64); + static_assert(SmallF::Config::kModulusBits <= 32); + + constexpr static size_t kNumFElements = BigF::Config::kModulusBits / 64; + constexpr static size_t R = kNumFElements * W; + + explicit MultiField32Challenger(Permutation&& permutation) + : permutation_(std::move(permutation)) {} + + private: + friend class Challenger>; + + // Challenger methods + void DoObserve(const SmallF& value) { + output_buffer_.clear(); + + input_buffer_.push_back(value); + + if (input_buffer_.size() == R) { + Duplex(); + } + } + + SmallF DoSample() { + if (!input_buffer_.empty() || output_buffer_.empty()) { + Duplex(); + } + + SmallF ret = std::move(output_buffer_.back()); + output_buffer_.pop_back(); + return ret; + } + + void Duplex() { + for (size_t i = 0; + i < (input_buffer_.size() + kNumFElements - 1) / kNumFElements; ++i) { + size_t start = i * kNumFElements; + size_t len = std::min(input_buffer_.size() - start, kNumFElements); + state_[i] = + Reduce(absl::Span(&input_buffer_[start], len)); + } + input_buffer_.clear(); + + permutation_.Permute(state_); + + output_buffer_.clear(); + for (size_t i = 0; i < W; ++i) { + std::array values = Split(state_[i]); + for (size_t j = 0; j < kNumFElements; ++j) { + output_buffer_.push_back(values[j]); + } + } + } + + crypto::SpongeState state_{W}; + absl::InlinedVector input_buffer_; + absl::InlinedVector output_buffer_; + Permutation permutation_; +}; + +template +struct ChallengerTraits> { + using Field = SmallF; +}; + +} // namespace tachyon::zk::air::plonky3 + +#endif // TACHYON_ZK_AIR_PLONKY3_CHALLENGER_MULTI_FIELD32_CHALLENGER_H_ diff --git a/tachyon/zk/air/plonky3/challenger/multi_field32_challenger_unittest.cc b/tachyon/zk/air/plonky3/challenger/multi_field32_challenger_unittest.cc new file mode 100644 index 000000000..7f3e6845a --- /dev/null +++ b/tachyon/zk/air/plonky3/challenger/multi_field32_challenger_unittest.cc @@ -0,0 +1,69 @@ +// Copyright (c) 2022 The Plonky3 Authors +// Use of this source code is governed by a MIT/Apache-2.0 style license that +// can be found in the LICENSE-MIT.plonky3 and the LICENCE-APACHE.plonky3 +// file. + +#include "tachyon/zk/air/plonky3/challenger/multi_field32_challenger.h" + +#include + +#include "gtest/gtest.h" + +#include "tachyon/crypto/hashes/sponge/poseidon2/poseidon2.h" +#include "tachyon/crypto/hashes/sponge/poseidon2/poseidon2_plonky3_external_matrix.h" +#include "tachyon/math/elliptic_curves/bn/bn254/poseidon2.h" +#include "tachyon/math/finite_fields/baby_bear/poseidon2.h" + +namespace tachyon::zk::air::plonky3 { + +using F = math::BabyBear; +using Poseidon2 = crypto::Poseidon2Sponge>>; + +namespace { + +class MultiField32ChallengerTest : public testing::Test { + public: + constexpr static size_t kWidth = 3; + + static void SetUpTestSuite() { + F::Init(); + math::bn254::Fr::Init(); + } + + void SetUp() override { + crypto::Poseidon2Config config = + crypto::Poseidon2Config::CreateCustom( + 2, 5, 8, 56, math::bn254::GetPoseidon2InternalDiagonalVector<3>()); + Poseidon2 sponge(config); + challenger_.reset( + new MultiField32Challenger(std::move(sponge))); + } + + protected: + std::unique_ptr> challenger_; +}; + +} // namespace + +TEST_F(MultiField32ChallengerTest, Sample) { + for (uint32_t i = 0; i < 20; ++i) { + challenger_->Observe(F(i)); + } + + F answers[] = { + F(72199253), F(733473132), F(442816494), F(326641700), F(1342573676), + F(1242755868), F(887300172), F(1831922292), F(1518709680), + }; + for (size_t i = 0; i < std::size(answers); ++i) { + EXPECT_EQ(challenger_->Sample(), answers[i]); + } +} + +TEST_F(MultiField32ChallengerTest, Grind) { + const uint32_t kBits = 3; + F witness = challenger_->Grind(kBits, base::Range(0, 100)); + EXPECT_TRUE(challenger_->CheckWitness(kBits, witness)); +} + +} // namespace tachyon::zk::air::plonky3