From 73398e0159a91f907b99959b15b63af7a48aae27 Mon Sep 17 00:00:00 2001 From: Mariano Sorgente Date: Thu, 25 Jun 2020 12:11:29 +0900 Subject: [PATCH] Remove challenge signature --- src/consensus/constants.py | 4 +- src/farmer.py | 53 ++++++----------- src/harvester.py | 59 ++++++------------- src/protocols/harvester_protocol.py | 4 +- src/types/proof_of_space.py | 19 +++--- tests/block_tools.py | 20 ++++--- tests/full_node/test_blockchain.py | 2 - .../full_node/test_blockchain_transactions.py | 9 +-- tests/full_node/test_full_node.py | 19 +----- tests/rpc/test_farmer_harvester_rpc.py | 13 ++-- tests/test_cost_calculation.py | 8 +-- tests/test_filter.py | 4 +- tests/test_merkle_set.py | 8 +-- tests/wallet/test_wallet.py | 2 +- tests/wallet_tools.py | 17 +++++- 15 files changed, 94 insertions(+), 147 deletions(-) diff --git a/src/consensus/constants.py b/src/consensus/constants.py index a57267a60686c..093ab76b5f02e 100644 --- a/src/consensus/constants.py +++ b/src/consensus/constants.py @@ -74,9 +74,9 @@ testnet_kwargs = { "DIFFICULTY_STARTING": 2 ** 31, "DIFFICULTY_FACTOR": 3, # The next difficulty is truncated to range [prev / FACTOR, prev * FACTOR] # These 3 constants must be changed at the same time - "DIFFICULTY_EPOCH": 128, # The number of blocks per epoch + "DIFFICULTY_EPOCH": 256, # The number of blocks per epoch "DIFFICULTY_WARP_FACTOR": 4, # DELAY divides EPOCH in order to warp efficiently. - "DIFFICULTY_DELAY": 32, # EPOCH / WARP_FACTOR + "DIFFICULTY_DELAY": 64, # EPOCH / WARP_FACTOR "SIGNIFICANT_BITS": 12, # The number of bits to look at in difficulty and min iters. The rest are zeroed "DISCRIMINANT_SIZE_BITS": 1024, # Max is 1024 (based on ClassGroupElement int size) "NUMBER_ZERO_BITS_CHALLENGE_SIG": 7, # H(plot signature of the challenge) must start with these many zeroes diff --git a/src/farmer.py b/src/farmer.py index b63058aa3d970..4d54c9f5b695f 100644 --- a/src/farmer.py +++ b/src/farmer.py @@ -2,7 +2,7 @@ import asyncio import logging from typing import Dict, List, Set, Optional, Callable, Tuple -from blspy import Util, InsecureSignature +from blspy import Util, InsecureSignature, PrependSignature from src.util.keychain import Keychain from src.consensus.constants import ConsensusConstants @@ -185,10 +185,6 @@ class Farmer: and request a pool partial, a header signature, or both, if the proof is good enough. """ - assert response.proof_of_possession.verify( - [Util.hash256(b"")], [response.harvester_pk] - ) - challenge_hash: bytes32 = response.proof.challenge_hash challenge_weight: uint128 = self.challenge_to_weight[challenge_hash] difficulty: uint64 = uint64(0) @@ -224,19 +220,6 @@ class Farmer: ) estimate_secs: float = number_iters / self.proof_of_time_estimate_ips - found = False - for pk in self._get_public_keys(): - if ( - ProofOfSpace.generate_plot_pubkey(response.harvester_pk, pk) - == response.proof.plot_pubkey - ): - found = True - if not found: - log.error( - f"Don't have the private key required for farming plot with plot pk: {response.proof.plot_pubkey.hex()}" - ) - return - if estimate_secs < self.config["pool_share_threshold"]: # TODO: implement pooling pass @@ -265,14 +248,23 @@ class Farmer: ] validates: bool = False for sk in self._get_private_keys(): - sig = sk.sign_insecure(header_hash) - agg_sig = InsecureSignature.aggregate(response.message_signature, sig) - - validates = agg_sig.verify( - [Util.hash256(header_hash)], [proof_of_space.plot_pubkey] + agg_pk = ProofOfSpace.generate_plot_pubkey( + response.harvester_pk, sk.get_public_key() ) - if validates: - break + if agg_pk == proof_of_space.plot_pubkey: + new_m = bytes(agg_pk) + Util.hash256(header_hash) + farmer_share = sk.sign_insecure(new_m) + agg_sig = PrependSignature.from_insecure_sig( + InsecureSignature.aggregate( + response.message_signature, farmer_share + ) + ) + + validates = agg_sig.verify( + [Util.hash256(new_m)], [proof_of_space.plot_pubkey] + ) + if validates: + break assert validates pos_hash: bytes32 = proof_of_space.get_hash() @@ -348,17 +340,8 @@ class Farmer: ] = proof_of_space_finalized.height if get_proofs: - signatures = [] - for sk in self._get_private_keys(): - signatures.append( - ( - sk.get_public_key(), - sk.sign_insecure(proof_of_space_finalized.challenge_hash), - ) - ) - message = harvester_protocol.NewChallenge( - proof_of_space_finalized.challenge_hash, signatures + proof_of_space_finalized.challenge_hash ) yield OutboundMessage( NodeType.HARVESTER, diff --git a/src/harvester.py b/src/harvester.py index 51a6c9fbf3697..581f60e42fa4a 100644 --- a/src/harvester.py +++ b/src/harvester.py @@ -28,7 +28,6 @@ class Harvester: config: Dict provers: Dict[Path, PlotInfo] failed_to_open_filenames: List[Path] - challenge_signatures: Dict[Tuple[bytes32, Path], InsecureSignature] no_key_filenames: List[Path] farmer_pubkeys: List[PublicKey] root_path: Path @@ -47,9 +46,6 @@ class Harvester: self.failed_to_open_filenames = [] self.no_key_filenames = [] - # From (challenge_hash, filename) to farmer signature - self.challenge_signatures = {} - self._is_shutdown = False self._plot_notification_task = None self.global_connections: Optional[PeerConnections] = None @@ -172,11 +168,7 @@ class Harvester: ChallengeResponse message is sent for each of the proofs found. """ start = time.time() - challenge_size = len(new_challenge.challenge_hash) - if challenge_size != 32: - raise ValueError( - f"Invalid challenge size {challenge_size}, 32 was expected" - ) + assert len(new_challenge.challenge_hash) == 32 loop = asyncio.get_running_loop() @@ -226,19 +218,12 @@ class Harvester: harvester_sig = plot_info.harvester_sk.sign_insecure( new_challenge.challenge_hash ) - agg_sig = None - for (farmer_pk, farmer_sig) in new_challenge.farmer_challenge_signatures: - if farmer_pk == plot_info.farmer_public_key: - agg_sig = InsecureSignature.aggregate(harvester_sig, farmer_sig) - if agg_sig is None: - log.error("Farmer does not have the correct keys for this plot") - return - - # This is actually secure, check proof_of_space.py for explanation - h = BitArray(std_hash(bytes(agg_sig))) - if h[: self.constants["NUMBER_ZERO_BITS_CHALLENGE_SIG"]].int == 0: + if ProofOfSpace.can_create_proof( + plot_info.prover.get_id(), + new_challenge.challenge_hash, + self.constants["NUMBER_ZERO_BITS_CHALLENGE_SIG"], + ): awaitables.append(lookup_challenge(filename, plot_info.prover)) - self.challenge_signatures[(new_challenge.challenge_hash, filename)] # Concurrently executes all lookups on disk, to take advantage of multiple disk parallelism for sublist_awaitable in asyncio.as_completed(awaitables): @@ -267,14 +252,6 @@ class Harvester: index = request.response_number proof_xs: bytes plot_info = self.provers[filename] - challenge_signature = self.challenge_signatures[(challenge_hash, filename)] - - assert ( - BitArray(std_hash(challenge_signature))[ - : self.constants["NUMBER_ZERO_BITS_CHALLENGE_SIG"] - ].int - == 0 - ) try: try: @@ -302,17 +279,11 @@ class Harvester: plot_info.farmer_address, plot_info.pool_address, plot_pubkey, - challenge_signature, uint8(self.provers[filename].get_size()), proof_xs, ) - proof_of_possession = plot_info.harvester_sk.sign_prepend(b"") response = harvester_protocol.RespondProofOfSpace( - request.plot_id, - request.response_number, - proof_of_space, - plot_info.harvester_sk.get_public_key(), - proof_of_possession, + request.plot_id, request.response_number, proof_of_space, ) if response: yield OutboundMessage( @@ -331,13 +302,21 @@ class Harvester: plot_info = self.provers[Path(request.plot_id).resolve()] plot_sk = plot_info.harvester_sk - signature: InsecureSignature = plot_sk.sign_insecure(request.message) - assert signature.verify( - [Util.hash256(request.message)], [plot_sk.get_public_key()] + agg_pk = ProofOfSpace.generate_plot_pubkey( + plot_sk.get_public_key(), plot_info.farmer_public_key ) + new_m = bytes(agg_pk) + Util.hash256(request.message) + + # This is only a partial signature. When combined with the farmer's half, it will + # form a complete PrependSignature. + signature: InsecureSignature = plot_sk.sign_insecure(new_m) response: harvester_protocol.RespondSignature = harvester_protocol.RespondSignature( - request.challenge_hash, request.plot_id, request.response_number, signature, + request.challenge_hash, + request.plot_id, + request.response_number, + plot_sk.get_public_key(), + signature, ) yield OutboundMessage( diff --git a/src/protocols/harvester_protocol.py b/src/protocols/harvester_protocol.py index 555726e5ffac1..4bcbe8b419e1f 100644 --- a/src/protocols/harvester_protocol.py +++ b/src/protocols/harvester_protocol.py @@ -24,7 +24,6 @@ class HarvesterHandshake: @cbor_message class NewChallenge: challenge_hash: bytes32 - farmer_challenge_signatures: List[Tuple[PublicKey, InsecureSignature]] @dataclass(frozen=True) @@ -51,8 +50,6 @@ class RespondProofOfSpace: plot_id: str response_number: uint8 proof: ProofOfSpace - harvester_pk: PublicKey - proof_of_possession: PrependSignature @dataclass(frozen=True) @@ -70,4 +67,5 @@ class RespondSignature: challenge_hash: bytes32 plot_id: str response_number: uint8 + harvester_pk: PublicKey message_signature: InsecureSignature diff --git a/src/types/proof_of_space.py b/src/types/proof_of_space.py index a59d79aee5b2e..b05bb4c635d93 100644 --- a/src/types/proof_of_space.py +++ b/src/types/proof_of_space.py @@ -18,7 +18,6 @@ class ProofOfSpace(Streamable): farmer_puzzle_hash: bytes32 pool_puzzle_hash: bytes32 plot_pubkey: PublicKey - challenge_signature: InsecureSignature size: uint8 proof: bytes @@ -33,17 +32,23 @@ class ProofOfSpace(Streamable): quality_str = v.validate_proof( plot_seed, self.size, self.challenge_hash, bytes(self.proof) ) - if not self.challenge_signature.verify( - [Util.hash256(self.challenge_hash)], [self.plot_pubkey] - ): - return None - h = BitArray(std_hash(bytes(self.challenge_signature))) - if h[:num_zero_bits].int != 0: + + if not self.can_create_proof(plot_seed, self.challenge_hash, num_zero_bits): return None + if not quality_str: return None return quality_str + @staticmethod + def can_create_proof( + plot_seed: bytes32, challenge_hash: bytes32, num_zero_bits: uint8 + ) -> bool: + h = BitArray(std_hash(bytes(challenge_hash) + bytes(plot_seed))) + if h[:num_zero_bits].int != 0: + return False + return True + @staticmethod def calculate_plot_seed( farmer_puzzle_hash: bytes32, diff --git a/tests/block_tools.py b/tests/block_tools.py index 33421dec7d466..047c7c4ba4b06 100644 --- a/tests/block_tools.py +++ b/tests/block_tools.py @@ -46,6 +46,8 @@ from src.util.mempool_check_conditions import get_name_puzzle_conditions from src.util.config import load_config, load_config_cli, save_config from src.util.plot_tools import load_plots, PlotInfo, stream_plot_info +from tests.wallet_tools import WalletTool + def get_plot_dir(): cache_path = ( @@ -83,7 +85,8 @@ class BlockTools: self.keychain = Keychain("testing-1.8", True) self.keychain.delete_all_keys() - self.keychain.add_private_key_seed(b"block_tools") + self.keychain.add_private_key_seed(b"block_tools farmer key") + self.keychain.add_private_key_seed(b"block_tools pool key") plot_dir = get_plot_dir() mkdir(plot_dir) @@ -126,7 +129,7 @@ class BlockTools: str(plot_dir), str(plot_dir), str(plot_dir), - filename, + str(filename), k, stream_plot_info( farmer_address, pool_address, farmer_pk, sk @@ -195,6 +198,14 @@ class BlockTools: farm_share = farmer_sk.sign_insecure(challenge_hash) return InsecureSignature.aggregate(harv_share, farm_share) + def get_farmer_wallet_tool(self): + esks = self.keychain.get_all_private_keys() + return WalletTool(esks[1][0]) + + def get_pool_wallet_tool(self): + esks = self.keychain.get_all_private_keys() + return WalletTool(esks[1][0]) + def get_consecutive_blocks( self, test_constants: ConsensusConstants, @@ -464,7 +475,6 @@ class BlockTools: selected_plot_info = None selected_proof_index = 0 selected_quality: Optional[bytes] = None - selected_challenge_signature = None best_quality = 0 plots = list(self.plots.values()) if self.use_any_pos: @@ -483,7 +493,6 @@ class BlockTools: if len(qualities) > 0 and first_bits == 0: selected_plot_info = plot_info selected_quality = qualities[0] - selected_challenge_signature = challenge_signature break else: for i in range(len(plots)): @@ -501,13 +510,11 @@ class BlockTools: if qual_int > best_quality and first_bits == 0: best_quality = qual_int selected_quality = quality - selected_challenge_signature = challenge_signature selected_plot_info = plot_info selected_proof_index = j j += 1 assert selected_plot_info is not None - assert selected_challenge_signature is not None if selected_quality is None: raise RuntimeError("No proofs for this challenge") @@ -523,7 +530,6 @@ class BlockTools: selected_plot_info.farmer_address, selected_plot_info.pool_address, plot_pk, - selected_challenge_signature, selected_plot_info.prover.get_size(), proof_xs, ) diff --git a/tests/full_node/test_blockchain.py b/tests/full_node/test_blockchain.py index 8e992f452fdce..b8290f006436b 100644 --- a/tests/full_node/test_blockchain.py +++ b/tests/full_node/test_blockchain.py @@ -258,7 +258,6 @@ class TestBlockValidation: blocks[9].proof_of_space.farmer_puzzle_hash, blocks[9].proof_of_space.pool_puzzle_hash, blocks[9].proof_of_space.plot_pubkey, - blocks[9].proof_of_space.challenge_signature, blocks[9].proof_of_space.size, bytes(bad_pos_proof), ) @@ -307,7 +306,6 @@ class TestBlockValidation: blocks[9].proof_of_space.farmer_puzzle_hash, blocks[9].proof_of_space.pool_puzzle_hash, blocks[9].proof_of_space.plot_pubkey, - blocks[9].proof_of_space.challenge_signature, blocks[9].proof_of_space.size, bytes(bad_pos_proof), ) diff --git a/tests/full_node/test_blockchain_transactions.py b/tests/full_node/test_blockchain_transactions.py index 1070b035d509f..f9b77c0501f0b 100644 --- a/tests/full_node/test_blockchain_transactions.py +++ b/tests/full_node/test_blockchain_transactions.py @@ -45,14 +45,11 @@ class TestBlockchainTransactions: @pytest.mark.asyncio async def test_basic_blockchain_tx(self, two_nodes): num_blocks = 10 - wallet_a = WalletTool() - coinbase_puzzlehash = wallet_a.get_new_puzzlehash() + wallet_a = bt.get_farmer_wallet_tool() wallet_receiver = WalletTool() receiver_puzzlehash = wallet_receiver.get_new_puzzlehash() - blocks = bt.get_consecutive_blocks( - test_constants, num_blocks, [], 10, b"", coinbase_puzzlehash - ) + blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10, b"") full_node_1, full_node_2, server_1, server_2 = two_nodes for block in blocks: @@ -90,7 +87,7 @@ class TestBlockchainTransactions: dic_h = {11: (program, aggsig)} new_blocks = bt.get_consecutive_blocks( - test_constants, 1, blocks, 10, b"", coinbase_puzzlehash, dic_h + test_constants, 1, blocks, 10, b"", dic_h ) next_block = new_blocks[11] diff --git a/tests/full_node/test_full_node.py b/tests/full_node/test_full_node.py index 6a75d02046280..d48186e938ac6 100644 --- a/tests/full_node/test_full_node.py +++ b/tests/full_node/test_full_node.py @@ -65,11 +65,8 @@ async def two_nodes(): async def wb(num_blocks, two_nodes): full_node_1, _, _, _ = two_nodes wallet_a = WalletTool() - coinbase_puzzlehash = wallet_a.get_new_puzzlehash() wallet_receiver = WalletTool() - blocks = bt.get_consecutive_blocks( - test_constants, num_blocks, [], 10, reward_puzzlehash=coinbase_puzzlehash - ) + blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10) for i in range(1, num_blocks): async for _ in full_node_1.respond_block(fnp.RespondBlock(blocks[i])): pass @@ -501,12 +498,7 @@ class TestFullNodeProtocol: blocks_list = await get_block_path(full_node_1) blocks_new = bt.get_consecutive_blocks( - test_constants, - 1, - blocks_list[:], - 4, - reward_puzzlehash=coinbase_puzzlehash, - seed=b"Another seed 4", + test_constants, 1, blocks_list[:], 4, seed=b"Another seed 4", ) for block in blocks_new: [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(block))] @@ -518,7 +510,6 @@ class TestFullNodeProtocol: 1, blocks_new[:], 4, - reward_puzzlehash=coinbase_puzzlehash, seed=i.to_bytes(4, "big") + b"Another seed", ) candidates.append(blocks_new_2[-1]) @@ -621,11 +612,7 @@ class TestFullNodeProtocol: # Don't propagate at old height [_ async for _ in full_node_1.respond_block(fnp.RespondBlock(candidates[0]))] blocks_new_3 = bt.get_consecutive_blocks( - test_constants, - 1, - blocks_new[:] + [candidates[0]], - 10, - reward_puzzlehash=coinbase_puzzlehash, + test_constants, 1, blocks_new[:] + [candidates[0]], 10, ) unf_block_new = FullBlock( blocks_new_3[-1].proof_of_space, diff --git a/tests/rpc/test_farmer_harvester_rpc.py b/tests/rpc/test_farmer_harvester_rpc.py index 27b17c03fbb88..7f2722e355957 100644 --- a/tests/rpc/test_farmer_harvester_rpc.py +++ b/tests/rpc/test_farmer_harvester_rpc.py @@ -2,8 +2,7 @@ import asyncio import pytest -from src.rpc.farmer_rpc_api import FarmerRpcApi -from src.rpc.harvester_rpc_api import HarvesterRpcApi +from secrets import token_bytes from blspy import PrivateKey from chiapos import DiskPlotter from src.types.proof_of_space import ProofOfSpace @@ -12,6 +11,9 @@ from src.rpc.harvester_rpc_client import HarvesterRpcClient from src.rpc.rpc_server import start_rpc_server from src.util.ints import uint16 from src.util.config import load_config +from src.rpc.farmer_rpc_api import FarmerRpcApi +from src.rpc.harvester_rpc_api import HarvesterRpcApi + from tests.setup_nodes import setup_farmer_harvester, test_constants, bt from tests.block_tools import get_plot_dir from tests.time_out_assert import time_out_assert @@ -91,11 +93,6 @@ class TestRpc: assert num_plots > 0 plot_dir = get_plot_dir() / "subdir" plotter = DiskPlotter() - pool_pk = harvester.pool_pubkeys[0] - plot_sk = PrivateKey.from_seed(b"Farmer harvester rpc test seed") - plot_seed = ProofOfSpace.calculate_plot_seed( - pool_pk, plot_sk.get_public_key() - ) filename = "test_farmer_harvester_rpc_plot.plot" plotter.create_plot_disk( str(plot_dir), @@ -104,7 +101,7 @@ class TestRpc: filename, 18, b"genesis", - plot_seed, + token_bytes(32), 128, ) diff --git a/tests/test_cost_calculation.py b/tests/test_cost_calculation.py index fc0afba2d119a..51dced5459179 100644 --- a/tests/test_cost_calculation.py +++ b/tests/test_cost_calculation.py @@ -24,13 +24,7 @@ class TestCostCalculation: num_blocks = 2 bt = BlockTools() - blocks = bt.get_consecutive_blocks( - test_constants, - num_blocks, - [], - 10, - reward_puzzlehash=wallet_tool.get_new_puzzlehash(), - ) + blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10,) spend_bundle = wallet_tool.generate_signed_transaction( blocks[1].get_coinbase().amount, diff --git a/tests/test_filter.py b/tests/test_filter.py index e881854f2b45e..f7b8947d848a0 100644 --- a/tests/test_filter.py +++ b/tests/test_filter.py @@ -31,9 +31,7 @@ class TestFilter: num_blocks = 2 ph = await wallet.get_new_puzzlehash() bt = BlockTools() - blocks = bt.get_consecutive_blocks( - test_constants, num_blocks, [], 10, reward_puzzlehash=ph, - ) + blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10) for i in range(1, num_blocks): byte_array_tx: List[bytes] = [] diff --git a/tests/test_merkle_set.py b/tests/test_merkle_set.py index e6c21016a5fa6..a006e46346b3a 100644 --- a/tests/test_merkle_set.py +++ b/tests/test_merkle_set.py @@ -21,13 +21,7 @@ class TestMerkleSet: num_blocks = 10 bt = BlockTools() - blocks = bt.get_consecutive_blocks( - test_constants, - num_blocks, - [], - 10, - reward_puzzlehash=wallet_tool.get_new_puzzlehash(), - ) + blocks = bt.get_consecutive_blocks(test_constants, num_blocks, [], 10,) merkle_set = MerkleSet() merkle_set_reverse = MerkleSet() diff --git a/tests/wallet/test_wallet.py b/tests/wallet/test_wallet.py index 8909a7f715cf8..b8fa2b2ff4547 100644 --- a/tests/wallet/test_wallet.py +++ b/tests/wallet/test_wallet.py @@ -136,7 +136,7 @@ class TestWalletSimulator: await time_out_assert(5, wallet.get_confirmed_balance, funds) await full_node_1.reorg_from_index_to_new_index( - ReorgProtocol(uint32(5), uint32(num_blocks + 3), token_bytes()) + ReorgProtocol(uint32(5), uint32(num_blocks + 3)) ) funds = sum( diff --git a/tests/wallet_tools.py b/tests/wallet_tools.py index d3a69b0ec99c9..ecf2713434367 100644 --- a/tests/wallet_tools.py +++ b/tests/wallet_tools.py @@ -37,11 +37,14 @@ class WalletTool: next_address = 0 pubkey_num_lookup: Dict[str, int] = {} - def __init__(self): + def __init__(self, esk: Optional[ExtendedPrivateKey] = None): self.current_balance = 0 self.my_utxos: set = set() - self.seed = urandom(1024) - self.extended_secret_key = ExtendedPrivateKey.from_seed(self.seed) + if esk is not None: + self.extended_secret_key = esk + else: + self.seed = urandom(1024) + self.extended_secret_key = ExtendedPrivateKey.from_seed(self.seed) self.generator_lookups: Dict = {} self.name = "MyChiaWallet" self.puzzle_pk_cache: Dict = {} @@ -86,6 +89,14 @@ class WalletTool: def puzzle_for_pk(self, pubkey): return puzzle_for_pk(pubkey) + def get_first_puzzle(self): + pubkey = self.extended_secret_key.public_child( + self.next_address + ).get_public_key() + puzzle = puzzle_for_pk(bytes(pubkey)) + self.puzzle_pk_cache[puzzle.get_tree_hash()] = 0 + return puzzle + def get_new_puzzle(self): pubkey_a = self.get_next_public_key() pubkey = bytes(pubkey_a)