Skip to content

Commit

Permalink
feat: support halo2 (de)serialization of point to/from proof
Browse files Browse the repository at this point in the history
Halo2 encodes point with a compression making use of a spare bit of base
field. But our copyable was a just memory copy including y and infinity
flag. To match with an outcome of Halo2 and furthermore support various
encoding scheme, `Transcript` class delegates encoding to a child class.
  • Loading branch information
chokobole committed Dec 15, 2023
1 parent 734d9b0 commit 71f78c3
Show file tree
Hide file tree
Showing 7 changed files with 276 additions and 10 deletions.
38 changes: 28 additions & 10 deletions tachyon/crypto/transcripts/transcript.h
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,22 @@ class TranscriptReaderImpl<Commitment, false> : public Transcript<Commitment> {
// Read a |commitment| from the proof. Note that it also writes the
// |commitment| to the transcript by calling |WriteToTranscript()| internally.
[[nodiscard]] bool ReadFromProof(Commitment* commitment) {
return buffer_.Read(commitment) && this->WriteToTranscript(*commitment);
return DoReadFromProof(commitment) && this->WriteToTranscript(*commitment);
}

// Read a |value| from the proof. Note that it also writes the
// |value| to the transcript by calling |WriteToTranscript()| internally.
[[nodiscard]] bool ReadFromProof(Field* value) {
return buffer_.Read(value) && this->WriteToTranscript(*value);
return DoReadFromProof(value) && this->WriteToTranscript(*value);
}

private:
protected:
// Read a |commitment| from the proof.
[[nodiscard]] virtual bool DoReadFromProof(Commitment* commitment) const = 0;

// Read a |value| from the proof.
[[nodiscard]] virtual bool DoReadFromProof(Field* value) const = 0;

base::Buffer buffer_;
};

Expand All @@ -125,10 +131,13 @@ class TranscriptReaderImpl<Field, true> : public Transcript<Field> {
// Read a |value| from the proof. Note that it also writes the
// |value| to the transcript by calling |WriteToTranscript()| internally.
[[nodiscard]] bool ReadFromProof(Field* value) {
return buffer_.Read(value) && this->WriteToTranscript(*value);
return DoReadFromProof(value) && this->WriteToTranscript(*value);
}

private:
protected:
// Read a |value| from the proof.
[[nodiscard]] virtual bool DoReadFromProof(Field* value) const = 0;

base::Buffer buffer_;
};

Expand All @@ -155,16 +164,22 @@ class TranscriptWriterImpl<Commitment, false> : public Transcript<Commitment> {
// Write a |commitment| to the proof. Note that it also writes the
// |commitment| to the transcript by calling |WriteToTranscript()| internally.
[[nodiscard]] bool WriteToProof(const Commitment& commitment) {
return this->WriteToTranscript(commitment) && buffer_.Write(commitment);
return this->WriteToTranscript(commitment) && DoWriteToProof(commitment);
}

// Write a |value| to the proof. Note that it also writes the
// |value| to the transcript by calling |WriteToTranscript()| internally.
[[nodiscard]] bool WriteToProof(const Field& value) {
return this->WriteToTranscript(value) && buffer_.Write(value);
return this->WriteToTranscript(value) && DoWriteToProof(value);
}

private:
protected:
// Write a |commitment| to the proof.
[[nodiscard]] virtual bool DoWriteToProof(const Commitment& commitment) = 0;

// Write a |value| to the proof.
[[nodiscard]] virtual bool DoWriteToProof(const Field& value) = 0;

base::Uint8VectorBuffer buffer_;
};

Expand All @@ -184,10 +199,13 @@ class TranscriptWriterImpl<Field, true> : public Transcript<Field> {
// Write a |value| to the proof. Note that it also writes the
// |value| to the transcript by calling |WriteToTranscript()| internally.
[[nodiscard]] bool WriteToProof(const Field& value) {
return this->WriteToTranscript(value) && buffer_.Write(value);
return this->WriteToTranscript(value) && DoWriteToProof(value);
}

private:
protected:
// Write a |value| to the proof.
[[nodiscard]] virtual bool DoWriteToProof(const Field& value) = 0;

base::Uint8VectorBuffer buffer_;
};

Expand Down
14 changes: 14 additions & 0 deletions tachyon/zk/plonk/halo2/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tachyon_cc_library(
hdrs = ["blake2b_transcript.h"],
deps = [
":constants",
":proof_serializer",
"//tachyon/crypto/transcripts:transcript",
"//tachyon/math/base:big_int",
"@com_google_boringssl//:crypto",
Expand Down Expand Up @@ -72,10 +73,21 @@ tachyon_cc_library(
hdrs = ["poseidon_transcript.h"],
deps = [
":poseidon_sponge",
":proof_serializer",
"//tachyon/crypto/transcripts:transcript",
],
)

tachyon_cc_library(
name = "proof_serializer",
hdrs = ["proof_serializer.h"],
deps = [
"//tachyon/base/buffer",
"//tachyon/math/elliptic_curves:points",
"//tachyon/math/finite_fields:prime_field_base",
],
)

tachyon_cc_library(
name = "prover",
hdrs = ["prover.h"],
Expand Down Expand Up @@ -116,6 +128,7 @@ tachyon_cc_library(
hdrs = ["sha256_transcript.h"],
deps = [
":constants",
":proof_serializer",
"//tachyon/base/types:always_false",
"//tachyon/crypto/transcripts:transcript",
"//tachyon/math/base:big_int",
Expand All @@ -129,6 +142,7 @@ tachyon_cc_unittest(
"blake2b_transcript_unittest.cc",
"pinned_verifying_key_unittest.cc",
"poseidon_transcript_unittest.cc",
"proof_serializer_unittest.cc",
"random_field_generator_unittest.cc",
"sha256_transcript_unittest.cc",
],
Expand Down
17 changes: 17 additions & 0 deletions tachyon/zk/plonk/halo2/blake2b_transcript.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#include "tachyon/crypto/transcripts/transcript.h"
#include "tachyon/math/base/big_int.h"
#include "tachyon/zk/plonk/halo2/constants.h"
#include "tachyon/zk/plonk/halo2/proof_serializer.h"

namespace tachyon::zk::halo2 {

Expand Down Expand Up @@ -68,6 +69,14 @@ class Blake2bReader : public crypto::TranscriptReader<AffinePointTy> {
}

private:
bool DoReadFromProof(AffinePointTy* point) const override {
return ProofSerializer<AffinePointTy>::ReadFromProof(this->buffer_, point);
}

bool DoReadFromProof(ScalarField* value) const override {
return ProofSerializer<ScalarField>::ReadFromProof(this->buffer_, value);
}

BLAKE2B_CTX state_;
};

Expand Down Expand Up @@ -119,6 +128,14 @@ class Blake2bWriter : public crypto::TranscriptWriter<AffinePointTy> {
}

private:
bool DoWriteToProof(const AffinePointTy& point) override {
return ProofSerializer<AffinePointTy>::WriteToProof(point, this->buffer_);
}

bool DoWriteToProof(const ScalarField& value) override {
return ProofSerializer<ScalarField>::WriteToProof(value, this->buffer_);
}

BLAKE2B_CTX state_;
};

Expand Down
17 changes: 17 additions & 0 deletions tachyon/zk/plonk/halo2/poseidon_transcript.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "tachyon/crypto/transcripts/transcript.h"
#include "tachyon/zk/plonk/halo2/poseidon_sponge.h"
#include "tachyon/zk/plonk/halo2/proof_serializer.h"

namespace tachyon::zk::halo2 {

Expand Down Expand Up @@ -45,6 +46,14 @@ class PoseidonReader : public crypto::TranscriptReader<AffinePointTy> {
}

private:
bool DoReadFromProof(AffinePointTy* point) const override {
return ProofSerializer<AffinePointTy>::ReadFromProof(this->buffer_, point);
}

bool DoReadFromProof(ScalarField* value) const override {
return ProofSerializer<ScalarField>::ReadFromProof(this->buffer_, value);
}

PoseidonSponge<ScalarField> state_;
};

Expand Down Expand Up @@ -78,6 +87,14 @@ class PoseidonWriter : public crypto::TranscriptWriter<AffinePointTy> {
}

private:
bool DoWriteToProof(const AffinePointTy& point) override {
return ProofSerializer<AffinePointTy>::WriteToProof(point, this->buffer_);
}

bool DoWriteToProof(const ScalarField& value) override {
return ProofSerializer<ScalarField>::WriteToProof(value, this->buffer_);
}

PoseidonSponge<ScalarField> state_;
};

Expand Down
85 changes: 85 additions & 0 deletions tachyon/zk/plonk/halo2/proof_serializer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// Copyright 2020-2022 The Electric Coin Company
// Copyright 2022 The Halo2 developers
// Use of this source code is governed by a MIT/Apache-2.0 style license that
// can be found in the LICENSE-MIT.halo2 and the LICENCE-APACHE.halo2
// file.

#ifndef TACHYON_ZK_PLONK_HALO2_PROOF_SERIALIZER_H_
#define TACHYON_ZK_PLONK_HALO2_PROOF_SERIALIZER_H_

#include <type_traits>
#include <utility>

#include "tachyon/base/buffer/buffer.h"
#include "tachyon/math/elliptic_curves/affine_point.h"
#include "tachyon/math/finite_fields/prime_field_base.h"

namespace tachyon::zk::halo2 {

template <typename F, typename SFINAE = void>
class ProofSerializer;

template <typename F>
class ProofSerializer<
F, std::enable_if_t<std::is_base_of_v<math::PrimeFieldBase<F>, F>>> {
public:
[[nodiscard]] static bool ReadFromProof(const base::Buffer& buffer,
F* value) {
return buffer.Read(value);
}

[[nodiscard]] static bool WriteToProof(const F& value, base::Buffer& buffer) {
return buffer.Write(value);
}
};

template <typename Curve>
class ProofSerializer<math::AffinePoint<Curve>> {
public:
using BaseField = typename math::AffinePoint<Curve>::BaseField;
using BigIntTy = typename BaseField::BigIntTy;

static_assert(BaseField::kModulusBits % 8 != 0,
"Halo2 needs 1 spare bit to put sign bit");

constexpr static size_t kByteSize = BaseField::kLimbNums * sizeof(uint64_t);

[[nodiscard]] static bool ReadFromProof(const base::Buffer& buffer,
math::AffinePoint<Curve>* point_out) {
uint8_t bytes[kByteSize];
if (!buffer.Read(bytes)) return false;
uint8_t is_odd = bytes[kByteSize - 1] >> 7;
bytes[kByteSize - 1] &= 0b01111111;
BaseField x = BaseField::FromBigInt(BigIntTy::FromBytesLE(bytes));
if (x.IsZero()) {
*point_out = math::AffinePoint<Curve>::Zero();
return true;
} else {
std::optional<math::AffinePoint<Curve>> point =
math::AffinePoint<Curve>::CreateFromX(x, is_odd);
if (!point.has_value()) return false;
*point_out = std::move(point).value();
return true;
}
}

[[nodiscard]] static bool WriteToProof(const math::AffinePoint<Curve>& point,
base::Buffer& buffer) {
if (point.infinity()) {
constexpr uint8_t kZeroBytes[kByteSize] = {
0,
};
return buffer.Write(kZeroBytes);
} else {
uint8_t is_odd = uint8_t{point.y().ToBigInt().IsOdd()} << 7;
std::array<uint8_t, kByteSize> x = point.x().ToBigInt().ToBytesLE();
if (!buffer.Write(x)) return false;
return buffer.WriteAt(buffer.buffer_offset() - 1,
static_cast<uint8_t>(x[kByteSize - 1] | is_odd));
}
}
};

} // namespace tachyon::zk::halo2

#endif // TACHYON_ZK_PLONK_HALO2_PROOF_SERIALIZER_H_
98 changes: 98 additions & 0 deletions tachyon/zk/plonk/halo2/proof_serializer_unittest.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// Copyright 2020-2022 The Electric Coin Company
// Copyright 2022 The Halo2 developers
// Use of this source code is governed by a MIT/Apache-2.0 style license that
// can be found in the LICENSE-MIT.halo2 and the LICENCE-APACHE.halo2
// file.

#include "tachyon/zk/plonk/halo2/proof_serializer.h"

#include <vector>

#include "gmock/gmock.h"
#include "gtest/gtest.h"

#include "tachyon/base/buffer/vector_buffer.h"
#include "tachyon/math/elliptic_curves/bn/bn254/g1.h"

namespace tachyon::zk::halo2 {

namespace {

using namespace math::bn254;

class ProofSerializerTest : public testing::Test {
public:
static void SetUpTestSuite() { G1Curve::Init(); }
};

} // namespace

TEST_F(ProofSerializerTest, SerializeScalar) {
struct {
std::string_view hex;
std::vector<uint8_t> proof;
} tests[] = {
{"0x2482c9ce1f365ed93c2afe1df9c673b0ba65278badd4d150f3b848cdd3d0cec8",
{200, 206, 208, 211, 205, 72, 184, 243, 80, 209, 212,
173, 139, 39, 101, 186, 176, 115, 198, 249, 29, 254,
42, 60, 217, 94, 54, 31, 206, 201, 130, 36}},
};

for (const auto& test : tests) {
std::vector<uint8_t> buffer;
buffer.resize(test.proof.size());
base::Buffer write_buf(buffer.data(), buffer.size());
Fr expected = Fr::FromHexString(test.hex);
ASSERT_TRUE(ProofSerializer<Fr>::WriteToProof(expected, write_buf));
EXPECT_THAT(buffer, testing::ElementsAreArray(test.proof));

write_buf.set_buffer_offset(0);
Fr actual;
ASSERT_TRUE(ProofSerializer<Fr>::ReadFromProof(write_buf, &actual));
EXPECT_EQ(actual, expected);
}
}

TEST_F(ProofSerializerTest, SerializeProof) {
struct {
std::array<std::string_view, 2> hex;
std::vector<uint8_t> proof;
} tests[] = {
// even point
{{
"0x233bd4dc42ffd123f6d041dca2117acea5f6a201b4612a81e7081cad001df470",
"0x14ecc49a7d74ee9059862ca5237c72f22dc6c39b64ec3e7c4ec314187577ee56",
},
{112, 244, 29, 0, 173, 28, 8, 231, 129, 42, 97,
180, 1, 162, 246, 165, 206, 122, 17, 162, 220, 65,
208, 246, 35, 209, 255, 66, 220, 212, 59, 35}},
// odd point
{{
"0x1ec72fa9df2846c267ad6bc77e438c0d8c0c9bba978be3095cc48b0334299dbb",
"0x2c1b5dfdca4dfc40a864355fead42fb3656a8a3304ad11b1dee1a4b924ac5a03",
},
{187, 157, 41, 52, 3, 139, 196, 92, 9, 227, 139,
151, 186, 155, 12, 140, 13, 140, 67, 126, 199, 107,
173, 103, 194, 70, 40, 223, 169, 47, 199, 158}},
};

for (const auto& test : tests) {
std::vector<uint8_t> buffer;
buffer.resize(test.proof.size());
base::Buffer write_buf(buffer.data(), buffer.size());
Fq x = Fq::FromHexString(test.hex[0]);
Fq y = Fq::FromHexString(test.hex[1]);
G1AffinePoint expected(x, y);
ASSERT_TRUE(
ProofSerializer<G1AffinePoint>::WriteToProof(expected, write_buf));
EXPECT_THAT(buffer, testing::ElementsAreArray(test.proof));

write_buf.set_buffer_offset(0);
G1AffinePoint actual;
ASSERT_TRUE(
ProofSerializer<G1AffinePoint>::ReadFromProof(write_buf, &actual));
EXPECT_EQ(actual, expected);
}
}

} // namespace tachyon::zk::halo2
Loading

0 comments on commit 71f78c3

Please sign in to comment.