diff --git a/.gitignore b/.gitignore index 8ba5e60ea..fd9c2b875 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ build .mypy_cache *.whl venv +build-tsan +build-* +cmake-build* \ No newline at end of file diff --git a/python-bindings/chiapos.cpp b/python-bindings/chiapos.cpp index 42431aa54..0671623d1 100644 --- a/python-bindings/chiapos.cpp +++ b/python-bindings/chiapos.cpp @@ -105,6 +105,7 @@ PYBIND11_MODULE(chiapos, m) return py::bytes(reinterpret_cast(id.data()), id.size()); }) .def("get_size", [](DiskProver &dp) { return dp.GetSize(); }) + .def("get_compresion_level", [](DiskProver &dp) { return dp.GetCompressionLevel(); }) .def("get_filename", [](DiskProver &dp) { return dp.GetFilename(); }) .def( "get_qualities_for_challenge", @@ -179,6 +180,10 @@ PYBIND11_MODULE(chiapos, m) delete[] quality_buf; return stdx::optional(quality_py); }); + + py::class_(m, "ContextQueue") + .def("init", &ContextQueue::init); + m.attr("decompresser_context_queue") = &decompresser_context_queue; } #endif // PYTHON_BINDINGS_PYTHON_BINDINGS_HPP_ diff --git a/src/cli.cpp b/src/cli.cpp index 6156dce4a..8619aba3c 100644 --- a/src/cli.cpp +++ b/src/cli.cpp @@ -63,6 +63,17 @@ void HelpAndQuit(cxxopts::Options options) exit(0); } +// Not thread safe +inline void InitDecompresserQueueDefault(bool no_cuda = false) +{ + static bool initialized = false; + if (initialized) { + return; + } + decompresser_context_queue.init(1, (uint32_t)std::thread::hardware_concurrency(), false, 9, !no_cuda, 0, false); + initialized = true; +} + int main(int argc, char *argv[]) try { cxxopts::Options options( "ProofOfSpace", "Utility for plotting, generating and verifying proofs of space."); @@ -161,6 +172,8 @@ int main(int argc, char *argv[]) try { num_threads, phases_flags); } else if (operation == "prove") { + InitDecompresserQueueDefault(); + if (argc < 3) { HelpAndQuit(options); } @@ -238,6 +251,8 @@ int main(int argc, char *argv[]) try { } delete[] proof_bytes; } else if (operation == "check") { + InitDecompresserQueueDefault(); + uint32_t iterations = 1000; if (argc == 3) { iterations = std::stoi(argv[2]); @@ -247,6 +262,8 @@ int main(int argc, char *argv[]) try { Verifier verifier = Verifier(); uint32_t success = 0; + uint32_t failures = 0; + uint32_t exceptions = 0; std::vector id_bytes = prover.GetId(); k = prover.GetSize(); @@ -257,10 +274,10 @@ int main(int argc, char *argv[]) try { vector hash(picosha2::k_digest_size); picosha2::hash256(hash_input.begin(), hash_input.end(), hash.begin(), hash.end()); - try { - vector qualities = prover.GetQualitiesForChallenge(hash.data()); + vector qualities = prover.GetQualitiesForChallenge(hash.data()); - for (uint32_t i = 0; i < qualities.size(); i++) { + for (uint32_t i = 0; i < qualities.size(); i++) { + try { LargeBits proof = prover.GetFullProof(hash.data(), i, parallel_read); uint8_t *proof_data = new uint8_t[proof.GetSize() / 8]; proof.ToBytes(proof_data); @@ -275,19 +292,22 @@ int main(int argc, char *argv[]) try { success++; } else { cout << "Proof verification failed." << endl; + failures += 1; } delete[] proof_data; + } catch (const std::exception& error) { + cout << "Threw: " << error.what() << endl; + exceptions += 1; } - } catch (const std::exception& error) { - cout << "Threw: " << error.what() << endl; - continue; } } std::cout << "Total success: " << success << "/" << iterations << ", " << (success * 100 / static_cast(iterations)) << "%." << std::endl; + std::cout << "Total failures: " << failures << std::endl; + std::cout << "Exceptions: " << exceptions << std::endl; if (show_progress) { progress(4, 1, 1); } } else { - cout << "Invalid operation. Use create/prove/verify/check" << endl; + cout << "Invalid operation '" << operation << "'. Use create/prove/verify/check" << endl; } return 0; } catch (const cxxopts::OptionException &e) { diff --git a/src/prover_disk.hpp b/src/prover_disk.hpp index 68808abc2..6f8c14beb 100644 --- a/src/prover_disk.hpp +++ b/src/prover_disk.hpp @@ -28,14 +28,23 @@ #include #include #include +#include +#include #include "../lib/include/picosha2.hpp" +#define uint32 uint32_t +#include "harvesting/GreenReaper.h" +#include "plotting/Compression.h" #include "calculate_bucket.hpp" #include "encoding.hpp" #include "entry_sizes.hpp" #include "serialize.hpp" #include "util.hpp" +#define CHIA_PLOT_V2_MAGIC 0x544F4C50ul // "PLOT" +#define CHIA_PLOT_VERSION_2_0_0 2 + + struct plot_header { uint8_t magic[19]; uint8_t id[32]; @@ -45,6 +54,98 @@ struct plot_header { }; +class ContextQueue { +public: + ContextQueue() {} + + bool init( + uint32_t context_count, + uint32_t thread_count, + bool no_cpu_affinity, + const uint32_t maxCompressionLevel, + bool use_gpu_harvesting, + uint32_t gpu_index, + bool enforce_gpu_index + ) { + GreenReaperConfig cfg = {}; + cfg.threadCount = thread_count; + cfg.disableCpuAffinity = no_cpu_affinity; + if (!use_gpu_harvesting) { + cfg.gpuRequest = GRGpuRequestKind_None; + } else { + if (enforce_gpu_index) { + cfg.gpuRequest = GRGpuRequestKind_ExactDevice; + } else { + cfg.gpuRequest = GRGpuRequestKind_FirstAvailable; + } + } + cfg.gpuDeviceIndex = gpu_index; + + for (uint32_t i = 0; i < context_count; i++) { + cfg.cpuOffset = i * thread_count; + auto gr = grCreateContext(&cfg); + + if (gr == nullptr) { + // Destroy contexts that were already created + while (!queue.empty()) { + grDestroyContext( queue.front() ); + queue.pop(); + } + throw std::logic_error("Failed to create GRContext"); + } + auto result = grPreallocateForCompressionLevel(gr, 32, maxCompressionLevel); + if (result != GRResult_OK) { + throw std::logic_error("Failed to allocate enough memory for contexts"); + } + queue.push(gr); + if (i == 0 && use_gpu_harvesting) { + if (grHasGpuDecompressor(gr)) { + return true; + } else { + // default to CPU + cfg.gpuRequest = GRGpuRequestKind_None; + } + } + } + return false; + } + + void push(GreenReaperContext* gr) { + std::unique_lock lock(mutex); + queue.push(gr); + lock.unlock(); + condition.notify_one(); + } + + GreenReaperContext* pop() { + std::unique_lock lock(mutex); + while (queue.empty()) { + condition.wait(lock); + } + dequeue_lock.lock(); + GreenReaperContext* gr = queue.front(); + queue.pop(); + dequeue_lock.unlock(); + return gr; + } + +private: + std::queue queue; + std::mutex mutex; + std::condition_variable condition; + std::mutex dequeue_lock; +}; + +ContextQueue decompresser_context_queue; + +struct CacheEntryProof { + // Input + uint8_t* challenge; + uint32_t index; + // Output + LargeBits full_proof; +}; + // The DiskProver, given a correctly formatted plot file, can efficiently generate valid proofs // of space, for a given challenge. class DiskProver { @@ -55,6 +156,8 @@ class DiskProver { explicit DiskProver(const std::string& filename) : id(kIdLen) { struct plot_header header{}; + this->compression_level = 0; + this->cache_entry_proof_position = 0; this->filename = filename; std::ifstream disk_file(filename, std::ios::in | std::ios::binary); @@ -70,27 +173,73 @@ class DiskProver { // 2 bytes - memo length // x bytes - memo - SafeRead(disk_file, (uint8_t*)&header, sizeof(header)); - if (memcmp(header.magic, "Proof of Space Plot", sizeof(header.magic)) != 0) - throw std::invalid_argument("Invalid plot header magic"); + // Check for V2 Magic. + uint8_t magic_2_bytes[4]; + SafeRead(disk_file, magic_2_bytes, 4); + uint32_t magic_2_result; + memcpy(&magic_2_result, magic_2_bytes, sizeof(magic_2_result)); + if (magic_2_result == CHIA_PLOT_V2_MAGIC) { + uint8_t version_bytes[4]; + SafeRead(disk_file, version_bytes, 4); + uint32_t version_result; + memcpy(&version_result, version_bytes, sizeof(version_result)); + if (version_result == CHIA_PLOT_VERSION_2_0_0) { + version = 2; + } + else { + throw std::invalid_argument("Unsupported version."); + } + } else { + // V1 + version = 1; + memcpy(header.magic, magic_2_bytes, sizeof(magic_2_bytes)); + uint8_t tmp_magic_buff[15]; + SafeRead(disk_file, tmp_magic_buff, sizeof(header.magic) - 4); + memcpy(header.magic + 4, tmp_magic_buff, sizeof(tmp_magic_buff)); + if (memcmp(header.magic, "Proof of Space Plot", sizeof(header.magic)) != 0) { + throw std::invalid_argument("Invalid plot header magic: " + Util::HexStr(header.magic, 19)); + } + } - uint16_t fmt_desc_len = Util::TwoBytesToInt(header.fmt_desc_len); + SafeRead(disk_file, (uint8_t*)&header.id, sizeof(header.id)); + SafeRead(disk_file, (uint8_t*)&header.k, sizeof(header.k)); - if (fmt_desc_len == kFormatDescription.size() && - !memcmp(header.fmt_desc, kFormatDescription.c_str(), fmt_desc_len)) { - // OK - } else { - throw std::invalid_argument("Invalid plot file format"); + if (version == 1) { + SafeRead(disk_file, (uint8_t*)&header.fmt_desc_len, sizeof(header.fmt_desc_len)); + SafeRead(disk_file, (uint8_t*)&header.fmt_desc, sizeof(header.fmt_desc)); + + uint16_t fmt_desc_len = Util::TwoBytesToInt(header.fmt_desc_len); + + if (fmt_desc_len == kFormatDescription.size() && + !memcmp(header.fmt_desc, kFormatDescription.c_str(), fmt_desc_len)) { + // OK + } else { + throw std::invalid_argument("Invalid plot file format"); + } + SafeSeek(disk_file, offsetof(struct plot_header, fmt_desc) + fmt_desc_len); } + memcpy(id.data(), header.id, sizeof(header.id)); this->k = header.k; - SafeSeek(disk_file, offsetof(struct plot_header, fmt_desc) + fmt_desc_len); uint8_t size_buf[2]; SafeRead(disk_file, size_buf, 2); memo.resize(Util::TwoBytesToInt(size_buf)); SafeRead(disk_file, memo.data(), memo.size()); + if (version == 2) { + uint8_t flags_bytes[4]; + SafeRead(disk_file, flags_bytes, sizeof(flags_bytes)); + uint32_t flags; + memcpy(&flags, flags_bytes, sizeof(flags)); + if (flags & 1) { + uint8_t compression_level; + SafeRead(disk_file, &compression_level, sizeof(compression_level)); + this->compression_level = compression_level; + std::cout << "Compression level: " << (int)compression_level << "\n"; + } + } + this->table_begin_pointers = std::vector(11, 0); this->C2 = std::vector(); @@ -134,7 +283,7 @@ class DiskProver { { Deserializer deserializer(vecBytes); deserializer >> version; - if (version != VERSION) { + if (version != 1 && version != 2) { // TODO: Migrate to new version if we change something related to the data structure throw std::invalid_argument("DiskProver: Invalid version."); } @@ -144,6 +293,12 @@ class DiskProver { deserializer >> k; deserializer >> table_begin_pointers; deserializer >> C2; + if (version == 2) { + deserializer >> compression_level; + } else { + compression_level = 0; + cache_entry_proof_position = 0; + } } DiskProver(DiskProver const&) = delete; @@ -154,9 +309,11 @@ class DiskProver { memo = std::move(other.memo); id = std::move(other.id); k = other.k; + compression_level = other.compression_level; table_begin_pointers = std::move(other.table_begin_pointers); C2 = std::move(other.C2); version = std::move(other.version); + cache_entry_proof_position = 0; } ~DiskProver() @@ -180,6 +337,56 @@ class DiskProver { uint8_t GetSize() const noexcept { return k; } + uint8_t GetCompressionLevel() const noexcept { return compression_level; } + + bool CompareProofBits(const LargeBits& left, const LargeBits& right, uint8_t k) + { + uint16_t size = left.GetSize() / k; + assert(left.GetSize() == right.GetSize()); + for (int16_t i = size - 1; i >= 0; i--) { + LargeBits left_val = left.Slice(k * i, k * (i + 1)); + LargeBits right_val = right.Slice(k * i, k * (i + 1)); + if (left_val < right_val) { + return true; + } + if (left_val > right_val) { + return false; + } + } + return false; + } + + LargeBits GetQualityStringFromProof( + LargeBits proof, + const uint8_t* challenge) + { + Bits challenge_bits = Bits(challenge, 256 / 8, 256); + uint16_t quality_index = challenge_bits.Slice(256 - 5).GetValue() << 1; + + // Converts the proof from proof ordering to plot ordering + for (uint8_t table_index = 1; table_index < 7; table_index++) { + LargeBits new_proof; + uint16_t size = k * (1 << (table_index - 1)); + for (int j = 0; j < (1 << (7 - table_index)); j += 2) { + LargeBits L = proof.Slice(j * size, (j + 1) * size); + LargeBits R = proof.Slice((j + 1) * size, (j + 2) * size); + if (CompareProofBits(L, R, k)) { + new_proof += (L + R); + } else { + new_proof += (R + L); + } + } + proof = new_proof; + } + // Hashes two of the x values, based on the quality index + std::vector hash_input(32 + Util::ByteAlign(2 * k) / 8, 0); + memcpy(hash_input.data(), challenge, 32); + proof.Slice(k * quality_index, k * (quality_index + 2)).ToBytes(hash_input.data() + 32); + std::vector hash(picosha2::k_digest_size); + picosha2::hash256(hash_input.begin(), hash_input.end(), hash.begin(), hash.end()); + return LargeBits(hash.data(), 32, 256); + } + // Given a challenge, returns a quality string, which is sha256(challenge + 2 adjecent x // values), from the 64 value proof. Note that this is more efficient than fetching all 64 x // values, which are in different parts of the disk. @@ -187,9 +394,10 @@ class DiskProver { { std::vector qualities; - std::lock_guard l(_mtx); + uint32_t p7_entries_size = 0; { + std::lock_guard l(_mtx); std::ifstream disk_file(filename, std::ios::in | std::ios::binary); if (!disk_file.is_open()) { @@ -203,15 +411,20 @@ class DiskProver { if (p7_entries.empty()) { return std::vector(); } + p7_entries_size = p7_entries.size(); // The last 5 bits of the challenge determine which route we take to get to // our two x values in the leaves. uint8_t last_5_bits = challenge[31] & 0x1f; for (uint64_t position : p7_entries) { + if (compression_level == 9) { + continue; + } // This inner loop goes from table 6 to table 1, getting the two backpointers, // and following one of them. - for (uint8_t table_index = 6; table_index > 1; table_index--) { + uint64_t alt_position; + for (uint8_t table_index = 6; table_index > GetEndTable(); table_index--) { uint128_t line_point = ReadLinePoint(disk_file, table_index, position); auto xy = Encoding::LinePointToSquare(line_point); @@ -219,13 +432,43 @@ class DiskProver { if (((last_5_bits >> (table_index - 2)) & 1) == 0) { position = xy.second; + alt_position = xy.first; } else { position = xy.first; + alt_position = xy.second; } } - uint128_t new_line_point = ReadLinePoint(disk_file, 1, position); - auto x1x2 = Encoding::LinePointToSquare(new_line_point); + uint128_t new_line_point = ReadLinePoint(disk_file, GetEndTable(), position); + std::pair x1x2; + if (compression_level > 0) { + GRCompressedQualitiesRequest req; + req.compressionLevel = compression_level; + req.plotId = id.data(); + req.challenge = challenge; + req.xLinePoints[0].hi = (uint64_t)(new_line_point >> 64); + req.xLinePoints[0].lo = (uint64_t)new_line_point; + if (compression_level >= 6) { + uint128_t alt_line_point = ReadLinePoint(disk_file, GetEndTable(), alt_position); + req.xLinePoints[1].hi = (uint64_t)(alt_line_point >> 64); + req.xLinePoints[1].lo = (uint64_t)alt_line_point; + } + + GreenReaperContext* gr = decompresser_context_queue.pop(); + assert(gr); + + auto res = grGetFetchQualitiesXPair(gr, &req); + decompresser_context_queue.push(gr); + if (res != GRResult_OK) { + // Expect this will result in failure in a later step. + x1x2.first = x1x2.second = 0; + } else { + x1x2.first = req.x1; + x1x2.second = req.x2; + } + } else { + x1x2 = Encoding::LinePointToSquare(new_line_point); + } // The final two x values (which are stored in the same location) are hashed std::vector hash_input(32 + Util::ByteAlign(2 * k) / 8, 0); memcpy(hash_input.data(), challenge, 32); @@ -236,9 +479,48 @@ class DiskProver { qualities.emplace_back(hash.data(), 32, 256); } } // Scope for disk_file + + if (compression_level == 9) { + uint8_t failure_bytes[32]; + for (int i = 0; i < 32; i++) { + failure_bytes[i] = 255; + } + for (uint32_t i = 0; i < p7_entries_size; i++) { + try { + auto proof = GetFullProof(challenge, i); + qualities.push_back(GetQualityStringFromProof(proof, challenge)); + } catch (const std::exception& error) { + qualities.emplace_back(failure_bytes, 32, 256); + } + } + } return qualities; } + bool FoundCachedProof(CacheEntryProof* proof_entry) { + std::lock_guard l(_cache_mtx); + for (uint32_t i = 0; i < cached_entry_proofs.size(); i++) { + if ( + memcmp(cached_entry_proofs[i].challenge, proof_entry->challenge, sizeof(proof_entry->challenge)) == 0 + && cached_entry_proofs[i].index == proof_entry->index + ) { + proof_entry->full_proof = cached_entry_proofs[i].full_proof; + return true; + } + } + return false; + } + + void AddCachedProof(CacheEntryProof proof_entry) { + std::lock_guard l(_cache_mtx); + if (cached_entry_proofs.size() < 1000) { + cached_entry_proofs.emplace_back(proof_entry); + } else { + cached_entry_proofs[cache_entry_proof_position] = proof_entry; + } + cache_entry_proof_position = (cache_entry_proof_position + 1) % 1000; + } + // Given a challenge, and an index, returns a proof of space. This assumes GetQualities was // called, and there are actually proofs present. The index represents which proof to fetch, // if there are multiple. @@ -246,6 +528,14 @@ class DiskProver { { LargeBits full_proof; + if (compression_level == 9) { + CacheEntryProof proof_entry; + memcpy(proof_entry.challenge, challenge, sizeof(challenge)); + proof_entry.index = index; + if (FoundCachedProof(&proof_entry)) { + return proof_entry.full_proof; + } + } std::lock_guard l(_mtx); { std::ifstream disk_file(filename, std::ios::in | std::ios::binary); @@ -262,11 +552,40 @@ class DiskProver { // Gets the 64 leaf x values, concatenated together into a k*64 bit string. std::vector xs; if (parallel_read) { - xs = GetInputs(p7_entries[index], 6); + xs = GetInputs(p7_entries[index], 6, nullptr); } else { xs = GetInputs(p7_entries[index], 6, &disk_file); // Passing in a disk_file disabled the parallel reads } + if (compression_level > 0) { + auto gr = decompresser_context_queue.pop(); + + uint32_t compressedProof[GR_POST_PROOF_CMP_X_COUNT] = {}; + uint8_t compressed_proof_size = (compression_level <= 8 ? GR_POST_PROOF_CMP_X_COUNT : (GR_POST_PROOF_CMP_X_COUNT / 2)); + for (int i = 0; i < compressed_proof_size; i++) { + compressedProof[i] = xs[i].GetValue(); + } + GRCompressedProofRequest req; + memcpy(req.compressedProof, compressedProof, sizeof(req.compressedProof)); + req.compressionLevel = compression_level; + req.plotId = id.data(); + + GRResult res = grFetchProofForChallenge(gr, &req); + decompresser_context_queue.push(gr); + + if (res != GRResult_OK) { + if (res == GRResult_NoProof) { + throw std::runtime_error("GRResult_NoProof received"); + } + throw std::runtime_error("GRResult is not GRResult_OK."); + } + std::vector uncompressed_xs; + for (int i = 0; i < GR_POST_PROOF_X_COUNT; i++) { + uncompressed_xs.push_back(Bits(req.fullProof[i], k)); + } + xs = uncompressed_xs; + } + // Sorts them according to proof ordering, where // f1(x0) m= f1(x1), f2(x0, x1) m= f2(x2, x3), etc. On disk, they are not stored in // proof ordering, they're stored in plot ordering, due to the sorting in the Compress @@ -276,6 +595,13 @@ class DiskProver { full_proof += x; } } // Scope for disk_file + if (compression_level == 9) { + CacheEntryProof proof_entry; + memcpy(proof_entry.challenge, challenge, sizeof(challenge)); + proof_entry.index = index; + proof_entry.full_proof = full_proof; + AddCachedProof(proof_entry); + } return full_proof; } @@ -283,16 +609,23 @@ class DiskProver { { Serializer serializer; serializer << version << filename << memo << id << k << table_begin_pointers << C2; + if (version == 2) { + serializer << compression_level; + } return serializer.Data(); } private: uint16_t version{VERSION}; mutable std::mutex _mtx; + mutable std::mutex _cache_mtx; std::string filename; std::vector memo; std::vector id; // Unique plot id + std::vector cached_entry_proofs; + uint32_t cache_entry_proof_position; uint8_t k; + uint8_t compression_level; std::vector table_begin_pointers; std::vector C2; @@ -328,14 +661,32 @@ class DiskProver { } } + uint8_t GetEndTable() { + if (compression_level == 0) { + return 1; + } + if (compression_level <= 8) { + return 2; + } + return 3; + } + // Reads exactly one line point (pair of two k bit back-pointers) from the given table. // The entry at index "position" is read. First, the park index is calculated, then // the park is read, and finally, entry deltas are added up to the position that we // are looking for. - uint128_t ReadLinePoint(std::ifstream& disk_file, uint8_t table_index, uint64_t position) - { + uint128_t ReadLinePoint( + std::ifstream& disk_file, + uint8_t table_index, + uint64_t position + ) { + CompressionInfo info; + bool is_compressed = compression_level > 0 && table_index == GetEndTable(); + if (is_compressed) { + info = GetCompressionInfoForLevel(compression_level); + } uint64_t park_index = position / kEntriesPerPark; - uint32_t park_size_bits = EntrySizes::CalculateParkSize(k, table_index) * 8; + uint32_t park_size_bits = (is_compressed ? info.tableParkSize : EntrySizes::CalculateParkSize(k, table_index)) * 8; SafeSeek(disk_file, table_begin_pointers[table_index] + (park_size_bits / 8) * park_index); @@ -346,12 +697,12 @@ class DiskProver { uint128_t line_point = Util::SliceInt128FromBytes(line_point_bin, 0, k * 2); // Reads EPP stubs - uint32_t stubs_size_bits = EntrySizes::CalculateStubsSize(k) * 8; + uint32_t stubs_size_bits = (is_compressed ? (Util::ByteAlign((kEntriesPerPark - 1) * info.subtSizeBits) / 8) : EntrySizes::CalculateStubsSize(k)) * 8; auto* stubs_bin = new uint8_t[stubs_size_bits / 8 + 7]; SafeRead(disk_file, stubs_bin, stubs_size_bits / 8); - // Reads EPP deltas - uint32_t max_deltas_size_bits = EntrySizes::CalculateMaxDeltasSize(k, table_index) * 8; + // Reads EPP deltas + uint32_t max_deltas_size_bits = (is_compressed ? info.tableParkSize - (line_point_size + stubs_size_bits) : EntrySizes::CalculateMaxDeltasSize(k, table_index)) * 8; auto* deltas_bin = new uint8_t[max_deltas_size_bits / 8]; // Reads the size of the encoded deltas object @@ -374,13 +725,13 @@ class DiskProver { SafeRead(disk_file, deltas_bin, encoded_deltas_size); // Decodes the deltas - double R = kRValues[table_index - 1]; + double R = (is_compressed ? info.ansRValue : kRValues[table_index - 1]); deltas = Encoding::ANSDecodeDeltas(deltas_bin, encoded_deltas_size, kEntriesPerPark - 1, R); } uint32_t start_bit = 0; - uint8_t stub_size = k - kStubMinusBits; + uint8_t stub_size = (is_compressed ? info.subtSizeBits : k - kStubMinusBits); uint64_t sum_deltas = 0; uint64_t sum_stubs = 0; for (uint32_t i = 0; @@ -724,7 +1075,7 @@ class DiskProver { } std::pair xy = Encoding::LinePointToSquare(line_point); - if (depth == 1) { + if (depth == GetEndTable()) { // For table P1, the line point represents two concatenated x values. std::vector ret; ret.emplace_back(xy.second, k); // y