// Copyright 2018 Chia Network Inc // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include #include #include "../lib/include/catch.hpp" #include "../lib/include/picosha2.hpp" #include "calculate_bucket.hpp" #include "plotter_disk.hpp" #include "sort_on_disk.hpp" #include "prover_disk.hpp" #include "verifier.hpp" #include "encoding.hpp" using namespace std; uint8_t plot_id_1[] = {35, 2, 52, 4, 51, 55, 23, 84, 91, 10, 111, 12, 13, 222, 151, 16, 228, 211, 254, 45, 92, 198, 204, 10, 9, 10, 11, 129, 139, 171, 15, 23}; uint8_t plot_id_3[] = {5, 104, 52, 4, 51, 55, 23, 84, 91, 10, 111, 12, 13, 222, 151, 16, 228, 211, 254, 45, 92, 198, 204, 10, 9, 10, 11, 129, 139, 171, 15, 23}; vector intToBytes(uint32_t paramInt, uint32_t numBytes) { vector arrayOfByte(numBytes, 0); for (uint32_t i = 0; paramInt > 0; i++) { arrayOfByte[numBytes - i - 1] = paramInt & 0xff; paramInt >>= 8; } return arrayOfByte; } TEST_CASE("Util") { SECTION("Increment and decrement") { uint8_t bytes[3] = {45, 172, 225}; REQUIRE(Util::SliceInt64FromBytes(bytes, 3, 2, 19) == 374172); uint8_t bytes2[1] = {213}; REQUIRE(Util::SliceInt64FromBytes(bytes2, 1, 1, 5) == 21); } } TEST_CASE("Bits") { SECTION("Increment and decrement") { Bits a = Bits(5, 3); Bits b = Bits(2, 10); cout << "A is: " << a << endl; cout << "B is: " << b << endl; ++b; ++b; ++b; ++b; cout << "B is: " << b << endl; ++a; cout << "A is: " << a << endl; ++a; cout << "A is: " << a << endl; cout << a + b + a << endl; --a; cout << "A is: " << a << endl; Bits c = a++; cout << "C is: " << c << endl; cout << "A is: " << a << endl; Bits d = a--; cout << "D is: " << d << endl; cout << "A is: " << a << endl; Bits e; Bits f = Bits(3, 5); cout << e + f + e + d << endl; } SECTION("Slicing and manipulating") { Bits g = Bits(13271, 15); cout << "G: " << g << endl; cout << "G Slice: " << g.Slice(4, 9) << endl; cout << "G Slice: " << g.Slice(0, 9) << endl; cout << "G Slice: " << g.Slice(9, 10) << endl; cout << "G Slice: " << g.Slice(9, 15) << endl; cout << "G Slice: " << g.Slice(9, 9) << endl; REQUIRE(g.Slice(9, 9) == Bits()); uint8_t bytes[2]; g.ToBytes(bytes); cout << "bytes: " << static_cast(bytes[0]) << " " << static_cast(bytes[1]) << endl; cout << "Back to Bits: " << Bits(bytes, 2, 16) << endl; Bits(256, 9).ToBytes(bytes); cout << "bytes: " << static_cast(bytes[0]) << " " << static_cast(bytes[1]) << endl; cout << "Back to Bits: " << Bits(bytes, 2, 16) << endl; cout << Bits(640, 11) << endl; Bits(640, 11).ToBytes(bytes); cout << "bytes: " << static_cast(bytes[0]) << " " << static_cast(bytes[1]) << endl; Bits h = Bits(bytes, 2, 16); Bits i = Bits(bytes, 2, 17); cout << "H: " << h << endl; cout << "I: " << i << endl; Bits j = Bits(11, 5); Bits k1 = Bits(7, 5); cout << "j" << j << endl; cout << "k" << k1 << endl; cout << "Xor:" << (j ^ k1) << endl; cout << "G: " << g << endl; cout << "size: " << g.GetSize() << endl; Bits shifted = (g << 150); REQUIRE(shifted.GetSize() == 15); REQUIRE(shifted.ToString() == "000000000000000"); Bits large = Bits(13271, 200); REQUIRE(large == ((large << 160)) >> 160); REQUIRE((large << 160).GetSize() == 200); Bits l = Bits(123287490, 20); l = l + Bits(0, 5); Bits m = Bits(5, 3); uint8_t buf[1]; m.ToBytes(buf); REQUIRE(buf[0] == (5 << 5)); } SECTION("Park Bits") { uint32_t num_bytes = 16000; uint8_t* buf = new uint8_t[num_bytes]; uint8_t* buf_2 = new uint8_t[num_bytes]; Util::GetRandomBytes(buf, num_bytes); ParkBits my_bits = ParkBits(buf, num_bytes, num_bytes*8); my_bits.ToBytes(buf_2); for (uint32_t i = 0; i < num_bytes; i++) { REQUIRE(buf[i] == buf_2[i]); } delete[] buf; delete[] buf_2; } SECTION("Large Bits") { uint32_t num_bytes = 200000; uint8_t* buf = new uint8_t[num_bytes]; uint8_t* buf_2 = new uint8_t[num_bytes]; Util::GetRandomBytes(buf, num_bytes); LargeBits my_bits = LargeBits(buf, num_bytes, num_bytes*8); my_bits.ToBytes(buf_2); for (uint32_t i = 0; i < num_bytes; i++) { REQUIRE(buf[i] == buf_2[i]); } delete[] buf; delete[] buf_2; } } class FakeDisk : public Disk { public: explicit FakeDisk(uint32_t size) : s(size, 'a') { f_ = std::stringstream(s, std::ios_base::in | std::ios_base::out); } void Read(uint64_t begin, uint8_t* memcache, uint32_t length) { f_.seekg(begin); f_.read(reinterpret_cast(memcache), length); } void Write(uint64_t begin, uint8_t* memcache, uint32_t length) { f_.seekp(begin); f_.write(reinterpret_cast(memcache), length); } std::iostream* ReadHandle(uint64_t begin) { f_.seekg(begin); return &f_; } std::iostream* WriteHandle(uint64_t begin) { f_.seekp(begin); return &f_; } private: std::string s; std::stringstream f_; }; bool CheckMatch(int64_t yl, int64_t yr) { int64_t bl = yl / kBC; int64_t br = yr / kBC; if (bl + 1 != br) return false; // Buckets don't match for (int64_t m = 0; m < kExtraBitsPow; m++) { if ((((yr % kBC) / kC - ((yl % kBC) / kC)) - m) % kB == 0) { if ((((yr % kBC) % kC - ((yl % kBC) % kC)) - (int64_t)(pow((double)(2*m + (bl%2)), (double)2))) % kC == 0) { return true; } } } return false; } TEST_CASE("F functions") { SECTION("F1") { uint8_t test_k = 35; uint8_t test_key[] = {0, 2, 3, 4, 5, 5, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 41, 5, 6, 7, 8, 9, 10, 11, 12, 13, 11, 15, 16}; F1Calculator f1(test_k, test_key); Bits L = Bits(525, test_k); pair result1 = f1.CalculateBucket(L); Bits L2 = Bits(526, test_k); pair result2 = f1.CalculateBucket(L2); Bits L3 = Bits(625, test_k); pair result3 = f1.CalculateBucket(L3); vector> results = f1.CalculateBuckets(L, 101); REQUIRE(result1 == results[0]); REQUIRE(result2 == results[1]); REQUIRE(result3 == results[100]); test_k = 32; F1Calculator f1_2(test_k, test_key); L = Bits(192837491, test_k); result1 = f1_2.CalculateBucket(L); L2 = Bits(192837491 + 1, test_k); result2 = f1_2.CalculateBucket(L2); L3 = Bits(192837491 + 2, test_k); result3 = f1_2.CalculateBucket(L3); Bits L4 = Bits(192837491 + 490, test_k); pair result4 = f1_2.CalculateBucket(L4); results = f1_2.CalculateBuckets(L, 491); REQUIRE(result1 == results[0]); REQUIRE(result2 == results[1]); REQUIRE(result3 == results[2]); REQUIRE(result4 == results[490]); } SECTION("F2") { uint8_t test_key_2[] = {20, 2, 5, 4, 51, 52, 23, 84, 91, 10, 111, 12, 13, 24, 151, 16, 228, 211, 254, 45, 92, 198, 204, 10, 9, 10, 11, 129, 139, 171, 15, 18}; map>> buckets; uint8_t k = 12; uint64_t num_buckets = pow(2, k + kExtraBits) / kBC + 1; Bits x = Bits(0, k); F1Calculator f1(k, test_key_2); for (uint32_t j=0; j < pow(2, k-4) + 1; j++) { for (auto pair : f1.CalculateBuckets(x, pow(2, 4))) { uint64_t bucket = std::get<0>(pair).GetValue() / kBC; if (buckets.find(bucket) == buckets.end()) { buckets[bucket] = vector>(); } buckets[bucket].push_back(pair); if (x.GetValue() + 1 > pow(2, k) - 1) { break; } ++x; } if (x.GetValue() + 1 > pow(2, k) - 1) { break; } } FxCalculator f2(k, 2, test_key_2); int total_matches = 0; for (auto kv : buckets) { if (kv.first == num_buckets- 1) { continue; } auto bucket_elements_2 = buckets[kv.first + 1]; vector left_bucket; vector right_bucket; for (auto yx1 : kv.second) { PlotEntry e; e.y = get<0>(yx1).GetValue(); left_bucket.push_back(e); } for (auto yx2 : buckets[kv.first + 1]) { PlotEntry e; e.y = get<0>(yx2).GetValue(); right_bucket.push_back(e); } sort(left_bucket.begin(), left_bucket.end(), [](const PlotEntry & a, const PlotEntry & b) -> bool { return a.y > b.y; }); sort(right_bucket.begin(), right_bucket.end(), [](const PlotEntry & a, const PlotEntry & b) -> bool { return a.y > b.y; }); vector > matches = f2.FindMatches(left_bucket, right_bucket); for (auto match : matches) { REQUIRE(CheckMatch(left_bucket[match.first].y, right_bucket[match.second].y)); } total_matches += matches.size(); } REQUIRE(total_matches == 3066); } } void HexToBytes(const string& hex, uint8_t* result) { for (unsigned int i = 0; i < hex.length(); i += 2) { string byteString = hex.substr(i, 2); uint8_t byte = (uint8_t) strtol(byteString.c_str(), NULL, 16); result[i/2] = byte; } } void TestProofOfSpace(std::string filename, uint32_t iterations, uint8_t k, uint8_t* plot_id, uint32_t expected_success) { DiskProver prover(filename); uint8_t* proof_data = new uint8_t[8 * k]; uint32_t success = 0; for (uint32_t i = 0; i < iterations; i++) { vector hash_input = intToBytes(i, 4); vector hash(picosha2::k_digest_size); picosha2::hash256(hash_input.begin(), hash_input.end(), hash.begin(), hash.end()); vector qualities = prover.GetQualitiesForChallenge(hash.data()); Verifier verifier = Verifier(); for (uint32_t index = 0; index < qualities.size(); index++) { LargeBits proof = prover.GetFullProof(hash.data(), index); proof.ToBytes(proof_data); LargeBits quality = verifier.ValidateProof(plot_id, k, hash.data(), proof_data, k*8); REQUIRE(quality.GetSize() == 2 * k); REQUIRE(quality == qualities[index]); success += 1; // Tests invalid proof proof_data[0] = (proof_data[0] + 1) % 256; LargeBits quality_2 = verifier.ValidateProof(plot_id, k, hash.data(), proof_data, k*8); REQUIRE(quality_2.GetSize() == 0); } } std::cout << "Success: " << success << "/" << iterations << " " << (100* ((double)success/(double)iterations)) << "%" << std::endl; REQUIRE(success == expected_success); delete[] proof_data; } void PlotAndTestProofOfSpace(std::string filename, uint32_t iterations, uint8_t k, uint8_t* plot_id, uint32_t expected_success) { DiskPlotter plotter = DiskPlotter(); uint8_t memo[5] = {1, 2, 3, 4, 5}; plotter.CreatePlotDisk(filename, k, memo, 5, plot_id, 32); TestProofOfSpace(filename, iterations, k, plot_id, expected_success); REQUIRE(remove(filename.c_str()) == 0); } TEST_CASE("Plotting") { SECTION("Disk plot 1") { PlotAndTestProofOfSpace("cpp-test-plot.dat", 100, 16, plot_id_1, 42); } SECTION("Disk plot 2") { PlotAndTestProofOfSpace("cpp-test-plot.dat", 500, 17, plot_id_3, 273); } SECTION("Disk plot 3") { PlotAndTestProofOfSpace("cpp-test-plot.dat", 5000, 21, plot_id_3, 4647); } } TEST_CASE("Invalid plot") { SECTION("File gets deleted") { DiskPlotter plotter = DiskPlotter(); uint8_t memo[5] = {1, 2, 3, 4, 5}; string filename = "invalid-plot.dat"; uint8_t k = 22; plotter.CreatePlotDisk(filename, k, memo, 5, plot_id_1, 32); DiskProver prover(filename); uint8_t* proof_data = new uint8_t[8 * k]; uint8_t challenge[32]; memset(challenge, 155, 32); vector qualities = prover.GetQualitiesForChallenge(challenge); Verifier verifier = Verifier(); REQUIRE(qualities.size() > 0); for (uint32_t index = 0; index < qualities.size(); index++) { LargeBits proof = prover.GetFullProof(challenge, index); proof.ToBytes(proof_data); LargeBits quality = verifier.ValidateProof(plot_id_1, k, challenge, proof_data, k*8); REQUIRE(quality == qualities[index]); } REQUIRE(remove(filename.c_str()) == 0); REQUIRE_THROWS_WITH([&](){ DiskProver p(filename); }(), "Invalid file " + filename); delete[] proof_data; } } TEST_CASE("Sort on disk") { SECTION("ExtractNum") { for (int i=0; i < 15*8 - 5; i++) { uint8_t buf[15]; Bits((uint128_t)27 << i, 15*8).ToBytes(buf); REQUIRE(SortOnDiskUtils::ExtractNum(buf, 15, 15*8 - 4 - i, 3) == 5); } uint8_t buf[16]; Bits((uint128_t)27 << 5, 128).ToBytes(buf); REQUIRE(SortOnDiskUtils::ExtractNum(buf, 16, 100, 200) == 864); } SECTION("MemCmpBits") { uint8_t left[3]; left[0] = 12; left[1] = 10; left[2] = 100; uint8_t right[3]; right[0] = 12; right[1] = 10; right[2] = 100; REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 0) == 0); REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 10) == 0); right[1] = 11; REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 0) < 0); REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 16) == 0); right[1] = 9; REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 0) > 0); right[1] = 10; // Last bit differs right[2] = 101; REQUIRE(SortOnDiskUtils::MemCmpBits(left, right, 3, 0) < 0); } SECTION("Quicksort") { uint32_t iters = 100; vector hashes; uint8_t* hashes_bytes = new uint8_t[iters * 16]; memset(hashes_bytes, 0, iters * 16); uint32_t random_state = 0; for (uint32_t i = 0; i < iters; i++) { string to_insert = std::to_string(rand_r(&random_state)); while (to_insert.length() < 16) { to_insert += "0"; } hashes.push_back(to_insert); memcpy(hashes_bytes + i * 16, to_insert.data(), to_insert.length()); } sort(hashes.begin(), hashes.end()); Sorting::QuickSort(hashes_bytes, 16, iters, 0); for (uint32_t i = 0; i < iters; i++) { std::string str(reinterpret_cast(hashes_bytes) + i * 16, 16); REQUIRE(str.compare(hashes[i]) == 0); } delete[] hashes_bytes; } SECTION("Fake disk") { FakeDisk d = FakeDisk(10000); uint8_t buf[5] = {1, 2, 3, 5, 7}; d.Write(250, buf, 5); uint8_t read_buf[5]; d.Read(250, read_buf, 5); REQUIRE(memcmp(buf, read_buf, 5) == 0); } SECTION("File disk") { FileDisk d = FileDisk("test_file.bin"); uint8_t buf[5] = {1, 2, 3, 5, 7}; d.Write(250, buf, 5); uint8_t read_buf[5]; d.Read(250, read_buf, 5); REQUIRE(memcmp(buf, read_buf, 5) == 0); remove("test_file.bin"); } SECTION("Bucket store") { uint32_t iters = 10000; uint32_t size = 16; vector input; for (uint32_t i = 0; i < iters; i++) { uint8_t rand_arr[size]; for (uint32_t i = 0; i < size; i++) { rand_arr[i] = rand() % 256; } input.push_back(Bits(rand_arr, size, size*8)); } set iset(input.begin(), input.end()); uint32_t input_index = 0; vector output; uint8_t* mem = new uint8_t[iters]; BucketStore bs = BucketStore(mem, iters, 16, 0, 4, 5); bs.Audit(); uint8_t buf[size]; while (true) { while (!bs.IsFull() && input_index != input.size()) { input[input_index].ToBytes(buf); bs.Store(buf, size); bs.Audit(); input_index += 1; } uint32_t m = bs.MaxBucket(); uint64_t final_size; uint128_t* bucket_handle = bs.BucketHandle(m, 1000000, final_size); uint32_t entry_size = size / 16; uint8_t last_size = (size * 8) % 128; if (last_size == 0) last_size = 128; for (uint32_t i = 0; i < final_size; i += entry_size) { Sorting::EntryToBytes(bucket_handle, i, i + entry_size, last_size, buf); Bits x(buf, size, size*8); REQUIRE(iset.find(x) != iset.end()); REQUIRE(SortOnDiskUtils::ExtractNum((uint8_t*)buf, size, 0, 4) == m); output.push_back(x); } if (bs.IsEmpty()) { delete[] bucket_handle; break; } delete[] bucket_handle; } sort(input.begin(), input.end()); sort(output.begin(), output.end()); set output_set(output.begin(), output.end()); REQUIRE(output_set.size() == output.size()); for (uint32_t i = 0; i < output.size(); i++) { REQUIRE(input[i] == output[i]); } delete[] mem; } SECTION("Sort on disk") { uint32_t iters = 100000; uint32_t size = 32; vector input; uint32_t begin = 1000; FakeDisk disk = FakeDisk(5000000); for (uint32_t i = 0; i < iters; i ++) { vector hash_input = intToBytes(i, 4); vector hash(picosha2::k_digest_size); picosha2::hash256(hash_input.begin(), hash_input.end(), hash.begin(), hash.end()); disk.Write(begin + i * size, hash.data(), size); input.emplace_back(Bits(hash.data(), size, size*8)); } vector bucket_sizes(16, 0); uint8_t buf[size]; for (Bits& x : input) { x.ToBytes(buf); bucket_sizes[SortOnDiskUtils::ExtractNum(buf, size, 0, 4)] += 1; } const uint32_t memory_len = 100000; uint8_t* memory = new uint8_t[memory_len]; Sorting::SortOnDisk(disk, begin, 3600000, size, 0, bucket_sizes, memory, memory_len); sort(input.begin(), input.end()); uint8_t buf2[size]; for (uint32_t i = 0; i < iters; i++) { disk.Read(begin + i * size, buf2, size); input[i].ToBytes(buf); REQUIRE(memcmp(buf, buf2, size) == 0); } delete[] memory; } SECTION("Sort in Memory") { uint32_t iters = 100000; uint32_t size = 32; vector input; uint32_t begin = 1000; FakeDisk disk = FakeDisk(5000000); for (uint32_t i = 0; i < iters; i ++) { vector hash_input = intToBytes(i, 4); vector hash(picosha2::k_digest_size); picosha2::hash256(hash_input.begin(), hash_input.end(), hash.begin(), hash.end()); hash[0] = hash[1] = 0; disk.Write(begin + i * size, hash.data(), size); input.emplace_back(Bits(hash.data(), size, size*8)); } const uint32_t memory_len = SortOnDiskUtils::RoundSize(iters) * 30; uint8_t* memory = new uint8_t[memory_len]; Sorting::SortInMemory(disk, begin, memory, size, iters, 16); sort(input.begin(), input.end()); uint8_t buf[size]; uint8_t buf2[size]; for (uint32_t i = 0; i < iters; i++) { disk.Read(begin + i * size, buf2, size); input[i].ToBytes(buf); REQUIRE(memcmp(buf, buf2, size) == 0); } delete[] memory; } }