diff --git a/src/python_bindings/fastvdf.cpp b/src/python_bindings/fastvdf.cpp index d715e3f0..a52c36ca 100644 --- a/src/python_bindings/fastvdf.cpp +++ b/src/python_bindings/fastvdf.cpp @@ -65,4 +65,35 @@ PYBIND11_MODULE(chiavdf, m) { py::bytes ret = py::bytes(reinterpret_cast(result.data()), result.size()); return ret; }); + + // Checks an N wesolowski proof, given y is given by 'GetB()' instead of a form. + m.def("verify_n_wesolowski_with_b", [] (const string& discriminant, + const string& B, + const string& x_s, + const string& proof_blob, + const uint64_t num_iterations, const uint64_t recursion) { + std::pair> result; + { + py::gil_scoped_release release; + std::string proof_blob_str(proof_blob); + uint8_t *proof_blob_ptr = reinterpret_cast(proof_blob_str.data()); + int proof_blob_size = proof_blob.size(); + result = CheckProofOfTimeNWesolowskiWithB(integer(discriminant), integer(B), (const uint8_t *)x_s.data(), proof_blob_ptr, proof_blob_size, num_iterations, recursion); + } + py::bytes res_bytes = py::bytes(reinterpret_cast(result.second.data()), result.second.size()); + py::tuple res_tuple = py::make_tuple(result.first, res_bytes); + return res_tuple; + }); + + m.def("get_b_from_n_wesolowski", [] (const string& discriminant, + const string& x_s, + const string& proof_blob, + const uint64_t num_iterations, const uint64_t recursion) { + py::gil_scoped_release release; + std::string proof_blob_str(proof_blob); + uint8_t *proof_blob_ptr = reinterpret_cast(proof_blob_str.data()); + int proof_blob_size = proof_blob.size(); + integer B = GetBFromProof(integer(discriminant), (const uint8_t *)x_s.data(), proof_blob_ptr, proof_blob_size, num_iterations, recursion); + return B.to_string(); + }); } diff --git a/src/verifier.h b/src/verifier.h index 54db122d..ea9e274c 100644 --- a/src/verifier.h +++ b/src/verifier.h @@ -76,4 +76,73 @@ bool CheckProofOfTimeNWesolowski(integer D, const uint8_t* x_s, const uint8_t* p return is_valid; } +bool CheckProofOfTimeNWesolowskiCommon(integer& D, form& x, const uint8_t* proof_blob, int32_t proof_blob_len, uint64_t& iterations, int last_segment, bool skip_check = false) { + int form_size = BQFC_FORM_SIZE; + int segment_len = 8 + B_bytes + form_size; + int i = proof_blob_len - segment_len; + PulmarkReducer reducer; + + for (; i >= last_segment; i -= segment_len) { + uint64_t segment_iters = BytesToInt64(&proof_blob[i]); + form proof = DeserializeForm(D, &proof_blob[i + 8 + B_bytes], form_size); + integer B(&proof_blob[i + 8], B_bytes); + form xnew; + if (!skip_check) { + if (VerifyWesoSegment(D, x, proof, B, segment_iters, xnew)) + return false; + } else { + integer L = root(-D, 4); + integer r = FastPow(2, segment_iters, B); + form f1 = FastPowFormNucomp(proof, D, B, L, reducer); + form f2 = FastPowFormNucomp(x, D, r, L, reducer); + xnew = f1 * f2; + } + + x = xnew; + if (segment_iters > iterations) { + return false; + } + iterations -= segment_iters; + } + return true; +} + +std::pair> CheckProofOfTimeNWesolowskiWithB(integer D, integer B, const uint8_t* x_s, const uint8_t* proof_blob, int32_t proof_blob_len, uint64_t iterations, int32_t depth) { + int form_size = BQFC_FORM_SIZE; + int segment_len = 8 + B_bytes + form_size; + form x = DeserializeForm(D, x_s, form_size); + std::vector result; + if (proof_blob_len != form_size + depth * segment_len) { + return {false, result}; + } + bool is_valid = CheckProofOfTimeNWesolowskiCommon(D, x, proof_blob, proof_blob_len, iterations, form_size); + if (is_valid == false) { + return {false, result}; + } + form proof = DeserializeForm(D, proof_blob, form_size); + form y_result; + if (VerifyWesoSegment(D, x, proof, B, iterations, y_result) == -1) { + return {false, result}; + } + int d_bits = D.num_bits(); + result = SerializeForm(y_result, d_bits); + return {true, result}; +} + +// TODO: Perhaps move? +integer GetBFromProof(integer D, const uint8_t* x_s, const uint8_t* proof_blob, int32_t proof_blob_len, uint64_t iterations, int32_t depth) { + int form_size = BQFC_FORM_SIZE; + int segment_len = 8 + B_bytes + form_size; + form x = DeserializeForm(D, x_s, form_size); + if (proof_blob_len != 2 * form_size + depth * segment_len) { + throw std::runtime_error("Invalid proof."); + } + bool is_valid = CheckProofOfTimeNWesolowskiCommon(D, x, proof_blob, proof_blob_len, iterations, 2 * form_size, true); + if (is_valid == false) { + throw std::runtime_error("Invalid proof."); + } + form y = DeserializeForm(D, proof_blob, form_size); + return GetB(D, x, y); +} + #endif // VERIFIER_H diff --git a/tests/test_n_weso_verifier.py b/tests/test_n_weso_verifier.py new file mode 100644 index 00000000..0701409b --- /dev/null +++ b/tests/test_n_weso_verifier.py @@ -0,0 +1,148 @@ +import secrets + +from chiavdf import ( + create_discriminant, + prove, + verify_wesolowski, + verify_n_wesolowski, + verify_n_wesolowski_with_b, + get_b_from_n_wesolowski, +) + + +def prove_n_weso(discriminant_challenge, x, discriminant_size, form_size, iters, witness, wrong_segm): + iters_chunk = iters // (witness + 1) + partials = [] + discriminant = create_discriminant(discriminant_challenge, discriminant_size) + for _ in range(witness): + result = prove(discriminant_challenge, x, discriminant_size, iters_chunk) + y = result[:form_size] + proof = result[form_size : 2 * form_size] + partials.append((x, y, proof)) + x = y + iters -= iters_chunk * witness + result = prove(discriminant_challenge, x, discriminant_size, iters) + y_result = result[:form_size] + y_proof = result[form_size : 2 * form_size] + assert verify_wesolowski(discriminant, x, y_result, y_proof, iters) + b_hex = get_b_from_n_wesolowski(discriminant, x, y_result + y_proof, iters, 0) + is_valid, y_from_compression = verify_n_wesolowski_with_b( + discriminant, + b_hex, + x, + y_proof, + iters, + 0, + ) + assert is_valid + assert y_from_compression == y_result + inner_proof = b"" + for x, y, proof in reversed(partials): + b_hex = get_b_from_n_wesolowski(discriminant, x, y + proof, iters_chunk, 0) + b = int(b_hex, 16) + assert verify_wesolowski(discriminant, x, y, proof, iters_chunk) + is_valid, y_from_compression = verify_n_wesolowski_with_b( + discriminant, + b_hex, + x, + proof, + iters_chunk, + 0, + ) + assert is_valid + assert y == y_from_compression + if not wrong_segm: + inner_proof += iters_chunk.to_bytes(8, byteorder='big') + else: + iters_wrong = iters_chunk + 1 + inner_proof += iters_wrong.to_bytes(8, byteorder='big') + wrong_segm = False + inner_proof += b.to_bytes(33, byteorder='big') + inner_proof += proof + return y_result, y_proof + inner_proof + + +def test_prove_n_weso_and_verify(): + discriminant_challenge = secrets.token_bytes(10) + discriminant_size = 512 + discriminant = create_discriminant(discriminant_challenge, discriminant_size) + form_size = 100 + initial_el = b"\x08" + (b"\x00" * 99) + + for iters in [1000000, 5000000, 10000000]: + y, proof = prove_n_weso(discriminant_challenge, initial_el, discriminant_size, form_size, iters, 5, False) + is_valid = verify_n_wesolowski( + str(discriminant), + initial_el, + y + proof, + iters, + discriminant_size, + 5, + ) + assert is_valid + is_valid = verify_n_wesolowski( + str(discriminant), + initial_el, + y + proof, + iters + 1, + discriminant_size, + 5, + ) + assert not is_valid + y, proof_wrong = prove_n_weso(discriminant_challenge, initial_el, discriminant_size, form_size, iters, 10, True) + is_valid = verify_n_wesolowski( + str(discriminant), + initial_el, + y + proof_wrong, + iters, + discriminant_size, + 10, + ) + assert not is_valid + b_hex = get_b_from_n_wesolowski(discriminant, initial_el, y + proof, iters, 5) + is_valid, y_from_compression = verify_n_wesolowski_with_b( + discriminant, + b_hex, + initial_el, + proof, + iters, + 5, + ) + assert is_valid + assert y_from_compression == y + B = str(int(b_hex, 16)) + is_valid, y_from_compression = verify_n_wesolowski_with_b( + discriminant, + B, + initial_el, + proof, + iters, + 5, + ) + assert is_valid + assert y_from_compression == y + B_wrong = str(int(b_hex, 16) + 1) + is_valid, y_from_compression = verify_n_wesolowski_with_b( + discriminant, + B_wrong, + initial_el, + proof, + iters, + 5, + ) + assert not is_valid + assert y_from_compression == b"" + is_valid, y_from_compression = verify_n_wesolowski_with_b( + discriminant, + B, + initial_el, + proof_wrong, + iters, + 10, + ) + assert not is_valid + assert y_from_compression == b"" + initial_el = y + + +test_prove_n_weso_and_verify()